From a750f158f5e5149e00193840e7284bb2e0bb769f Mon Sep 17 00:00:00 2001 From: David Ibia Date: Sat, 3 Oct 2026 16:27:47 +0100 Subject: [PATCH] refactor(opencode): restructure config and replace legacy plugins Rewrites opencode.json, drops the with-context, websearch and terminal-bell plugins, adds plugins that strip Claude-only commands and OMC MCP servers, and vendors the impeccable, web-design-guidelines and frontend-workflow skills. --- .config/opencode/DEVELOPMENT-WORKFLOW.md | 37 + .config/opencode/README.md | 65 + .config/opencode/UI-WORKFLOW.md | 5 + .config/opencode/archived/with-context.ts | 1285 -- .config/opencode/command/impeccable.md | 6 + .config/opencode/opencode.json | 573 +- .../lib/claude-only-command-registry.ts | 55 + .config/opencode/plugin/package.json | 8 - .../plugin/strip-claude-only-commands.ts | 15 + .config/opencode/plugin/strip-omc-mcp.ts | 15 + .config/opencode/plugin/terminal-bell.ts | 12 - .config/opencode/plugin/websearch.ts | 73 - .config/opencode/plugin/with-context.ts | 1 - .../skills/frontend-workflow/SKILL.md | 50 + .../references/dotfiles-workbench.md | 13 + .config/opencode/skills/impeccable/LICENSE | 191 + .config/opencode/skills/impeccable/SKILL.md | 90 + .../skills/impeccable/reference/adapt.md | 318 + .../impeccable/reference/adapt.native.md | 58 + .../skills/impeccable/reference/android.md | 46 + .../skills/impeccable/reference/animate.md | 89 + .../skills/impeccable/reference/audit.md | 137 + .../impeccable/reference/audit.native.md | 139 + .../skills/impeccable/reference/bolder.md | 33 + .../skills/impeccable/reference/clarify.md | 94 + .../skills/impeccable/reference/colorize.md | 86 + .../impeccable/reference/craft-floor.md | 44 + .../skills/impeccable/reference/craft.md | 5 + .../skills/impeccable/reference/critique.md | 806 + .../reference/degraded/asset-producer.md | 37 + .../reference/degraded/documenter.md | 24 + .../reference/degraded/finish-reviewer.md | 38 + .../reference/degraded/manual-edit-applier.md | 92 + .../skills/impeccable/reference/delight.md | 70 + .../skills/impeccable/reference/distill.md | 111 + .../skills/impeccable/reference/doctor.md | 54 + .../skills/impeccable/reference/document.md | 416 + .../skills/impeccable/reference/extract.md | 69 + .../skills/impeccable/reference/generate.md | 101 + .../skills/impeccable/reference/harden.md | 345 + .../skills/impeccable/reference/hooks.md | 111 + .../skills/impeccable/reference/init.md | 131 + .../skills/impeccable/reference/ios.md | 51 + .../skills/impeccable/reference/layout.md | 84 + .../skills/impeccable/reference/live-setup.md | 104 + .../skills/impeccable/reference/live.md | 325 + .../skills/impeccable/reference/new-work.md | 145 + .../skills/impeccable/reference/onboard.md | 234 + .../skills/impeccable/reference/operate.md | 61 + .../skills/impeccable/reference/optimize.md | 258 + .../skills/impeccable/reference/overdrive.md | 127 + .../skills/impeccable/reference/polish.md | 105 + .../skills/impeccable/reference/quieter.md | 99 + .../skills/impeccable/reference/routing.md | 24 + .../skills/impeccable/reference/shape.md | 59 + .../skills/impeccable/reference/typeset.md | 80 + .../skills/impeccable/reference/visualize.md | 46 + .../skills/impeccable/scripts/VERSION | 1 + .../impeccable/scripts/command-metadata.json | 98 + .../scripts/data/font-index-failures.json | 121 + .../impeccable/scripts/data/font-index.json | 1 + .../skills/impeccable/scripts/impeccable | 206 + .../skills/impeccable/scripts/impeccable.cmd | 214 + .../impeccable/scripts/live-browser-dom.js | 167 + .../scripts/live-browser-ignores.js | 242 + .../scripts/live-browser-session.js | 144 + .../skills/impeccable/scripts/live-browser.js | 13510 ++++++++++++++++ .../scripts/modern-screenshot.umd.js | 14 + .../skills/web-design-guidelines/SKILL.md | 39 + .../claude-only-command-registry.test.ts | 35 + .config/opencode/tui.json | 5 + 71 files changed, 21048 insertions(+), 1499 deletions(-) create mode 100644 .config/opencode/DEVELOPMENT-WORKFLOW.md create mode 100644 .config/opencode/README.md create mode 100644 .config/opencode/UI-WORKFLOW.md delete mode 100644 .config/opencode/archived/with-context.ts create mode 100644 .config/opencode/command/impeccable.md create mode 100644 .config/opencode/plugin/lib/claude-only-command-registry.ts delete mode 100644 .config/opencode/plugin/package.json create mode 100644 .config/opencode/plugin/strip-claude-only-commands.ts create mode 100644 .config/opencode/plugin/strip-omc-mcp.ts delete mode 100644 .config/opencode/plugin/terminal-bell.ts delete mode 100644 .config/opencode/plugin/websearch.ts delete mode 120000 .config/opencode/plugin/with-context.ts create mode 100644 .config/opencode/skills/frontend-workflow/SKILL.md create mode 100644 .config/opencode/skills/frontend-workflow/references/dotfiles-workbench.md create mode 100644 .config/opencode/skills/impeccable/LICENSE create mode 100644 .config/opencode/skills/impeccable/SKILL.md create mode 100644 .config/opencode/skills/impeccable/reference/adapt.md create mode 100644 .config/opencode/skills/impeccable/reference/adapt.native.md create mode 100644 .config/opencode/skills/impeccable/reference/android.md create mode 100644 .config/opencode/skills/impeccable/reference/animate.md create mode 100644 .config/opencode/skills/impeccable/reference/audit.md create mode 100644 .config/opencode/skills/impeccable/reference/audit.native.md create mode 100644 .config/opencode/skills/impeccable/reference/bolder.md create mode 100644 .config/opencode/skills/impeccable/reference/clarify.md create mode 100644 .config/opencode/skills/impeccable/reference/colorize.md create mode 100644 .config/opencode/skills/impeccable/reference/craft-floor.md create mode 100644 .config/opencode/skills/impeccable/reference/craft.md create mode 100644 .config/opencode/skills/impeccable/reference/critique.md create mode 100644 .config/opencode/skills/impeccable/reference/degraded/asset-producer.md create mode 100644 .config/opencode/skills/impeccable/reference/degraded/documenter.md create mode 100644 .config/opencode/skills/impeccable/reference/degraded/finish-reviewer.md create mode 100644 .config/opencode/skills/impeccable/reference/degraded/manual-edit-applier.md create mode 100644 .config/opencode/skills/impeccable/reference/delight.md create mode 100644 .config/opencode/skills/impeccable/reference/distill.md create mode 100644 .config/opencode/skills/impeccable/reference/doctor.md create mode 100644 .config/opencode/skills/impeccable/reference/document.md create mode 100644 .config/opencode/skills/impeccable/reference/extract.md create mode 100644 .config/opencode/skills/impeccable/reference/generate.md create mode 100644 .config/opencode/skills/impeccable/reference/harden.md create mode 100644 .config/opencode/skills/impeccable/reference/hooks.md create mode 100644 .config/opencode/skills/impeccable/reference/init.md create mode 100644 .config/opencode/skills/impeccable/reference/ios.md create mode 100644 .config/opencode/skills/impeccable/reference/layout.md create mode 100644 .config/opencode/skills/impeccable/reference/live-setup.md create mode 100644 .config/opencode/skills/impeccable/reference/live.md create mode 100644 .config/opencode/skills/impeccable/reference/new-work.md create mode 100644 .config/opencode/skills/impeccable/reference/onboard.md create mode 100644 .config/opencode/skills/impeccable/reference/operate.md create mode 100644 .config/opencode/skills/impeccable/reference/optimize.md create mode 100644 .config/opencode/skills/impeccable/reference/overdrive.md create mode 100644 .config/opencode/skills/impeccable/reference/polish.md create mode 100644 .config/opencode/skills/impeccable/reference/quieter.md create mode 100644 .config/opencode/skills/impeccable/reference/routing.md create mode 100644 .config/opencode/skills/impeccable/reference/shape.md create mode 100644 .config/opencode/skills/impeccable/reference/typeset.md create mode 100644 .config/opencode/skills/impeccable/reference/visualize.md create mode 100644 .config/opencode/skills/impeccable/scripts/VERSION create mode 100644 .config/opencode/skills/impeccable/scripts/command-metadata.json create mode 100644 .config/opencode/skills/impeccable/scripts/data/font-index-failures.json create mode 100644 .config/opencode/skills/impeccable/scripts/data/font-index.json create mode 100755 .config/opencode/skills/impeccable/scripts/impeccable create mode 100644 .config/opencode/skills/impeccable/scripts/impeccable.cmd create mode 100644 .config/opencode/skills/impeccable/scripts/live-browser-dom.js create mode 100644 .config/opencode/skills/impeccable/scripts/live-browser-ignores.js create mode 100644 .config/opencode/skills/impeccable/scripts/live-browser-session.js create mode 100644 .config/opencode/skills/impeccable/scripts/live-browser.js create mode 100644 .config/opencode/skills/impeccable/scripts/modern-screenshot.umd.js create mode 100644 .config/opencode/skills/web-design-guidelines/SKILL.md create mode 100644 .config/opencode/tests/claude-only-command-registry.test.ts create mode 100644 .config/opencode/tui.json diff --git a/.config/opencode/DEVELOPMENT-WORKFLOW.md b/.config/opencode/DEVELOPMENT-WORKFLOW.md new file mode 100644 index 0000000..98d51dc --- /dev/null +++ b/.config/opencode/DEVELOPMENT-WORKFLOW.md @@ -0,0 +1,37 @@ +# Automatic development workflow + +Apply this routing policy to each new user goal without requiring a command or skill name. Explicit user instructions and project requirements take precedence. This is an instruction-based workflow, not a tool-permission enforcement mechanism. + +## Choose the smallest appropriate route + +- Questions and explanation-only requests: answer without making changes. Do not reinterpret curiosity as authorisation to implement. +- Trivial, fully specified edits (typos, formatting, exact configuration values): inspect, edit, and verify directly. Do not start a brainstorming ceremony. +- Bug fixes that restore established behaviour: load `systematic-debugging`, investigate, fix narrowly, and verify through the affected surface. If the fix requires a new product decision or architectural change, switch to the design route before implementing that change. +- New features, meaningful behaviour changes, ambiguous requirements, or significant architectural changes: automatically enter the design route below. Judge risk and semantics, not just file count. A one-line authorisation change is not a trivial edit. +- An already approved spec and plan: resume execution without restarting brainstorming. Confirm approval applies to the current scope and revision. + +For substantive work, briefly name the chosen route. If classification is uncertain, inspect context read-only first; ask one focused question only if the answer changes scope or the approval boundary. Reassess when new evidence materially changes the task. + +## Design and approval + +Load `brainstorming` (or `superpowers:brainstorming` if unavailable). Explore the existing implementation, clarify material unknowns, compare credible approaches, and recommend one. Keep the process proportional to the task. + +Before implementation, produce a concise written specification and implementation plan covering goal, non-goals, behaviour, acceptance criteria, affected components, risks, dependencies, and verification. Use the project's documentation conventions; otherwise use `docs/superpowers/specs/` and `docs/superpowers/plans/`. Load `superpowers:writing-plans` when available to structure the plan. + +Present the current spec and plan for explicit approval, then stop. Read-only exploration and writing planning documents are allowed before approval; implementation, scaffolding, dependency changes, and delegating implementation are not. A request to build a feature is not approval of an unseen design. Approval already given for the same presented scope counts; do not ask twice. Material scope changes require renewed approval. Never commit merely because a skill says to; commit only when the user requests it. + +This automatic route does not invoke `ulw-plan`, Prometheus, Metis, Momus, or `start-work` as a shortcut around their activation requirements. Preserve their explicit opt-in gates. If the user explicitly chooses one of those workflows, follow its own rules rather than running two planning processes. + +## Approved execution and review + +For an approved plan with separable implementation tasks, load `superpowers:subagent-driven-development` when available and use OMO task delegation. Give each worker the approved spec, acceptance criteria, dependencies, owned files, and required evidence. Keep a single writer per file set. Parallelise only independent work supported by the harness; run dependent work sequentially. Keep a single coherent edit local rather than inventing a team. + +Workers execute their assigned scope; they do not restart the top-level brainstorming or approval loop. They report material scope gaps to the parent. The parent reviews results against the spec, then reviews code quality, runs relevant checks, and exercises the matching user-facing surface. Do not treat a worker's success claim as verification. If delegation is unavailable, execute sequentially and disclose that limitation. + +For substantive UI work, also follow `UI-WORKFLOW.md` and load `frontend-workflow`; preserve its ownership and bounded visual review rules. Do not duplicate an already approved design process. + +## TypeSafe and Jev + +Load the installed `typesafe-ai` skill when designing or implementing semantic routing, ranking, extraction, or verification with TypeSafe, and consult its live documentation. Jev can suggest task categories or relevant skills using typed judgments. It is not a replacement for a reasoning/coding agent, spec review, tests, or observed user approval. + +The default workflow requires no TypeSafe API calls. Installing its skill does not enable an API integration. Before adding one, agree on data sharing and credentials; do not send source code, secrets, or private conversations to a new service by default. Keep permissions, opt-in gates, and approval state explicit outside probabilistic judgments. Uncertain suggestions must not bypass those boundaries. diff --git a/.config/opencode/README.md b/.config/opencode/README.md new file mode 100644 index 0000000..4a39eb9 --- /dev/null +++ b/.config/opencode/README.md @@ -0,0 +1,65 @@ +# OpenCode development workflows + +## Automatic task routing + +`DEVELOPMENT-WORKFLOW.md` is loaded through OpenCode's `instructions` and managed by `link-ai-configs.sh`. Questions stay read-only, exact small edits stay lightweight, and bugs enter systematic debugging. Features and significant behaviour or architecture changes enter brainstorming, a written spec and plan, and explicit approval before implementation. Approved separable tasks use subagents with file ownership and spec-compliance and code-quality review. Existing UI workflow rules still apply. + +This is model-followed policy, not a plugin-enforced permission gate. `ulw-plan` remains explicitly opt-in. Restart OpenCode after changing the instructions. + +The TypeSafe skill can be installed for OpenCode with `npx skills add typesafe-ai/skills --skill typesafe-ai --agent opencode --global --yes`. Its Jev model is useful for optional semantic task/skill suggestions, not generating specifications or granting approval. The default router makes no TypeSafe API calls and requires no API key. See the [TypeSafe skill-suggestion cookbook](https://docs.typesafe.ai/cookbooks/skill_suggestion.md) before considering a separate integration. + +## GPT visual workflow + +This setup keeps the existing GPT models and adds a bounded, evidence-led frontend process. It does not promise better results than Claude Code. + +## Installed integration + +- OpenCode 1.18.31 and Oh My OpenAgent (OMO) 4.19.4 were inspected and exercised. +- Impeccable skill 4.3.1 is vendored unchanged from the upstream compiled `.opencode/skills/impeccable` integration at [pbakaus/impeccable revision f2c7051853848826aac2f4646581d62a732155ad](https://github.com/pbakaus/impeccable/tree/f2c7051853848826aac2f4646581d62a732155ad/.opencode). Its native slash command is in `command/impeccable.md`; its Apache 2.0 license is included. The installed launcher reports engine `4.0.0`; both `context` and `detect` ran successfully. Skill, npm launcher and engine version numbers are distinct. +- The documented command `npx impeccable@4.1.0 install -y --providers=opencode --scope=global --no-hooks` timed out downloading its payload. Installation therefore used the pinned, committed OpenCode integration artifacts above, through OpenCode's documented global skill and command directories, rather than inventing another skill format. +- Vercel's `web-design-guidelines` SKILL.md comes from [vercel-labs/agent-skills revision 063bee94c3f4df8453406c830b0a7df0f2860278](https://github.com/vercel-labs/agent-skills/blob/063bee94c3f4df8453406c830b0a7df0f2860278/skills/web-design-guidelines/SKILL.md). It fetches the current [Web Interface Guidelines](https://raw.githubusercontent.com/vercel-labs/web-interface-guidelines/main/command.md) when invoked, so its rule feed is intentionally not pinned. +- `frontend-workflow` is the small local process skill. Its dotfiles-workbench reference distinguishes repository evidence from proposed fixture defaults; no approved web brand or screenshot reference was invented. + +`link-ai-configs.sh` links the three managed skill directories into `~/.config/opencode/skills/`, plus `UI-WORKFLOW.md`. Old OpenCode Impeccable 3.6 and duplicate Vercel copies were archived. Claude's design-skill paths now alias the same canonical Impeccable and Vercel source, preserving discovery without divergent copies. Unrelated skills and Claude configuration were not changed. + +## Default behaviour and ownership + +The short `UI-WORKFLOW.md` trigger is loaded through OpenCode `instructions`. It loads the workflow skill only for substantive UI tasks. OMO's existing `visual-engineering` category retains `openai/gpt-6-astra` with high reasoning and uses the supported `prompt_append` field for delegation context. No unsupported category `skills` property was added. + +One owner implements and corrects a file set. Browser inspection and critique are separate sequential passes, with a normal maximum of three. A delegated handoff supplies purpose, primary action, design/reference paths, acceptance criteria, run command, phase, ownership and remaining budget. Read-only reviewers return findings, not concurrent edits. The user brief and established design system override optional aesthetic bans or arbitrary perfect-score demands. + +Existing Hephaestus, OpenAI and z.ai model assignments are unchanged. GPT workers inspect images directly; the GLM `multimodal-looker` remains available for other work and is not silently substituted for GPT visual judgement. + +## Browser and screenshot delivery + +The configured Playwright MCP is pinned to `@playwright/mcp@0.0.81`, using installed Chrome, an isolated headless profile and `--image-responses allow`. Its CLI help confirmed these flags; the fresh OpenCode server reported it connected. Existing Chrome DevTools configuration was preserved, including its independent profile-lock limitation. + +A fresh OpenCode GPT-6 Astra session called the configured `playwright_browser_navigate`, `playwright_browser_resize` and `playwright_browser_take_screenshot` tools. The screenshot tool returned an actual `image/png` data attachment. GPT identified a purple circle, teal triangle, orange square, and random six-character code `658FDB` without inspecting DOM/source or reading the file. Session: `ses_f4f2b153cffeGEf87dvdhGTgZc`. + +Some wrappers return only a screenshot filename. In that case the worker must explicitly read the image or attach it to the GPT session before claiming visual inspection. A separate attachment test and three-screenshot critique also succeeded. Never infer image receipt merely from a tool success message. + +## Verification evidence + +Artifacts are under `~/Projects/Research/opencode-design-evaluation-2026-09-17/`: + +- `runtime-evidence.json`: fresh skill discovery, agent models, MCP connection, actual skill invocations and recorded session usage. +- `updated/`: representative build, screenshot critique, correction, and fresh `pass2-*.png` desktop/mobile/intermediate and interaction-state captures. +- `controlled-before/` and `controlled-after/`: separate initial builds from the same brief and starting context with explicit working directories. +- `EVALUATION.md` and `HUMAN-REVIEW.md`: measured results, limitations and an unscored blind preference form. + +The representative page passed `node --check app.js`, JavaScript diagnostics, desktop/mobile Run/loading/disabled/error/retry/success/filter/empty/reset checks, visible keyboard focus, reduced motion and long-text wrapping. No horizontal overflow at 1280, 768 or 390px. Sampled rendered text contrast was at least 6.7:1. Two independent GPT reviewers approved the final source/evidence and all 13 fresh screenshots, without a numeric taste score. + +The critic found two material issues rather than fabricating a third. Corrections moved scenario before Run and reduced mobile setup overhead; the first check starts at 628px instead of roughly 840px. The process stopped after two screenshot rounds. Impeccable's mechanical detector retained one advisory about repeated safety-disclosure copy. Full screen-reader, cross-browser and production performance testing were not performed on this disposable static fixture. + +## Rollback + +Pre-change snapshots are in `~/.local/state/opencode-design/2026-09-17/`. Do not use a repository-wide Git reset: unrelated changes predate this work. + +1. Quit OpenCode. Preserve any changes made since installation before restoring snapshots. +2. Restore `omo.jsonc` to `~/dotfiles/.config/omo/omo.jsonc`, `opencode.json` to `~/dotfiles/.config/opencode/opencode.json`, `link-ai-configs.sh` to the repository root, and `dotfiles-README.md` to the root README. +3. Remove only the newly created symlinks `~/.config/opencode/UI-WORKFLOW.md` and `~/.config/opencode/skills/{frontend-workflow,impeccable,web-design-guidelines}`. Leave their source directories intact if you want to retain the work for inspection. +4. Restore `retired-opencode-impeccable` to `~/.opencode/skills/impeccable` and `retired-opencode-web-design-guidelines` to `~/.config/opencode/skill/web-design-guidelines`. +5. Remove only the two new Claude skill symlinks, then restore `retired-claude-impeccable` and `retired-claude-web-design-guidelines` to their original directories under `~/.claude/skills/`. +6. Move the newly added `~/dotfiles/.config/opencode/command/impeccable.md` out of the command scan root. Restart OpenCode. No model credentials, accounts or unrelated settings need changing. + +For future upgrades, review upstream changes and preserve this contextual workflow. Do not merge an old Impeccable script tree into a newer one. After changes, restart OpenCode and repeat the native screenshot smoke test and discovery checks. diff --git a/.config/opencode/UI-WORKFLOW.md b/.config/opencode/UI-WORKFLOW.md new file mode 100644 index 0000000..5f71a6c --- /dev/null +++ b/.config/opencode/UI-WORKFLOW.md @@ -0,0 +1,5 @@ +# Substantive UI work + +For interface implementation, redesign, or substantial styling, load `frontend-workflow` on demand before editing. Do not load its body or all design skills for unrelated work. With OMO, delegate implementation through `visual-engineering`, including the brief, design context/reference paths, acceptance criteria, run command, phase, and sole writer's file ownership. A direct GPT worker may perform the same sequential passes without another agent. + +The user's brief and established product design system override stylistic bans, mandatory decorative treatments, unnecessary research, and arbitrary perfect-score requirements in optional design skills. Fonts, gradients, cards, and motion must serve the product. Respect explicit time/phase limits; do not turn a small resolved brief into a planning ceremony. Never count a screenshot filename, DOM snapshot, or tool success as visual inspection: deliver and inspect image pixels with the GPT worker. Use a normally three-pass maximum and report what remains. diff --git a/.config/opencode/archived/with-context.ts b/.config/opencode/archived/with-context.ts deleted file mode 100644 index 80decf5..0000000 --- a/.config/opencode/archived/with-context.ts +++ /dev/null @@ -1,1285 +0,0 @@ -import type { Plugin } from '@opencode-ai/plugin'; -import { tool } from '@opencode-ai/plugin'; -import { - writeNote as mcpWriteNote, - readNote as mcpReadNote, - listNotes as mcpListNotes, - searchNotes as mcpSearchNotes, - healthCheck as mcpHealthCheck, - setProjectContext as mcpSetProjectContext, - getNoteMetadata as mcpGetNoteMetadata, - deleteNote as mcpDeleteNote, - batchWriteNotes as mcpBatchWriteNotes, - listTemplatesHandler as mcpListTemplates, - createFromTemplateHandler as mcpCreateFromTemplate, - ingestNotes as mcpIngestNotes, - syncNotes as mcpSyncNotes, - teleportNotes as mcpTeleportNotes, - // Session management tools - startSession as mcpStartSession, - pauseSession as mcpPauseSession, - resumeSession as mcpResumeSession, - endSession as mcpEndSession, - getSessionStatus as mcpGetSessionStatus, - // Changelog and todo tools - addChangelogEntry as mcpAddChangelogEntry, - getSessionChangelog as mcpGetSessionChangelog, - getCommitSuggestion as mcpGetCommitSuggestion, - addTodo as mcpAddTodo, - updateTodo as mcpUpdateTodo, - listTodos as mcpListTodos, - // Configuration tools - setupNotes as mcpSetupNotes, - validateConfigTool as mcpValidateConfig, - previewDelegationTool as mcpPreviewDelegation, - // Vault organization tools - analyzeVaultStructureHandler as mcpAnalyzeVaultStructure, - reorganizeNotesHandler as mcpReorganizeNotes, - generateOrganizationPlanHandler as mcpGenerateOrganizationPlan, - // Content editing tools - updateFrontmatter as mcpUpdateFrontmatter, - replaceSection as mcpReplaceSection, -} from 'with-context-mcp/tools'; - -/** - * WithContext OpenCode Plugin - Enhanced Version - * - * Provides project-scoped note management for OpenCode sessions - * Integrates with Obsidian and other note-taking apps via with-context-mcp - * - * All tools are defined inline for easy distribution and deployment - * - * Note: Full auto-tracking capabilities require OpenCode plugin API enhancements. - * Current version provides all MCP tools as native OpenCode tools. - */ -export const WithContextPlugin: Plugin = async ({ project: _project, directory: _directory }) => { - // Initialize plugin state - const config = { - vaultPath: process.env.OBSIDIAN_VAULT_PATH || process.env.HOME + '/Documents/Vault', - basePath: process.env.PROJECT_BASE_PATH || 'Projects', - }; - - return { - // Event hook for session lifecycle - event: async ({ event }) => { - // Silent cleanup on session idle - if (event.type === 'session.idle') { - // No-op: cleanup if needed - // Note: Full session status display requires access to SessionManager - // which needs to be initialized within tool context - } - }, - - // Custom tools - all defined inline - tool: { - // ==================== Status Tool ==================== - with_context_status: tool({ - description: 'Check WithContext plugin status and configuration', - args: {}, - async execute(_args, _ctx) { - return JSON.stringify( - { - status: 'active', - config, - version: '3.0.6', - tools: 32, - custom_commands: 3, - features: { - filename_slugification: true, - path_resolution: 'auto-detect-from-git', - ascii_only_output: true, - type_safety: 'zero-warnings', - project_folder_optional: '26 tools auto-detect project', - }, - note: 'Most tools auto-detect project from git context. Session lifecycle tools still require explicit project_folder.', - }, - null, - 2 - ); - }, - }), - - // ==================== Write Note Tool ==================== - write_note: tool({ - description: - 'Write or update a markdown note in the project folder. Supports create, overwrite, append, and prepend modes. Filenames are automatically slugified (lowercase, spaces/special chars become hyphens). Well-known files like README, CHANGELOG, LICENSE preserve their case.', - args: { - path: tool.schema - .string() - .describe( - 'Relative path to the note within the project folder (e.g., "CHANGELOG.md" or "docs/api.md"). Filenames are auto-slugified: "My Notes" becomes "my-notes.md"' - ), - content: tool.schema.string().describe('Content to write to the note'), - mode: tool.schema - .enum(['create', 'overwrite', 'append', 'prepend']) - .optional() - .describe( - 'Write mode: create (fail if exists), overwrite (replace), append (add to end), or prepend (add to beginning). Default: overwrite' - ), - }, - async execute(args, _ctx) { - try { - const result = await mcpWriteNote({ - path: args.path, - content: args.content, - mode: args.mode || 'overwrite', - }); - return result; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - return JSON.stringify({ success: false, error: message }, null, 2); - } - }, - }), - - // ==================== Read Note Tool ==================== - read_note: tool({ - description: 'Read the content of a markdown note from the project folder.', - args: { - path: tool.schema - .string() - .describe( - 'Relative path to the note within the project folder (e.g., "CHANGELOG.md" or "docs/api.md")' - ), - }, - async execute(args, _ctx) { - try { - const result = await mcpReadNote({ - path: args.path, - }); - return result; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - return JSON.stringify({ success: false, error: message }, null, 2); - } - }, - }), - - // ==================== List Notes Tool ==================== - list_notes: tool({ - description: - 'List all notes in a folder within the project. Supports fuzzy finding to search for notes by filename.', - args: { - path: tool.schema - .string() - .optional() - .describe('Optional: Relative path to a subfolder (defaults to project root)'), - fuzzy_query: tool.schema - .string() - .optional() - .describe( - 'Optional: Fuzzy search query to filter notes by filename (e.g., "test" matches "test-file.md", "testing.md")' - ), - limit: tool.schema - .number() - .optional() - .describe('Optional: Maximum number of results to return (default: 50)'), - min_score: tool.schema - .number() - .optional() - .describe( - 'Optional: Minimum fuzzy match score threshold 0-1 (default: 0.3, higher = stricter)' - ), - include_highlights: tool.schema - .boolean() - .optional() - .describe('Optional: Include match highlights in results (default: true)'), - }, - async execute(args, _ctx) { - try { - const result = await mcpListNotes({ - path: args.path, - fuzzy_query: args.fuzzy_query, - limit: args.limit ?? 50, - min_score: args.min_score, - include_highlights: args.include_highlights ?? true, - }); - return result; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - return JSON.stringify({ success: false, error: message }, null, 2); - } - }, - }), - - // ==================== Update Frontmatter Tool ==================== - update_frontmatter: tool({ - description: - 'Update frontmatter in a markdown note. Supports merge mode (add/update fields while preserving others) or replace mode (overwrite entire frontmatter). Enforces read-before-write.', - args: { - path: tool.schema - .string() - .describe('Relative path to the note within the project folder'), - frontmatter: tool.schema - .record(tool.schema.string(), tool.schema.unknown()) - .describe('Frontmatter fields to add or update as key-value pairs'), - mode: tool.schema - .enum(['merge', 'replace']) - .describe( - 'Update mode: merge (add/update fields, preserve others) or replace (overwrite entire frontmatter)' - ), - project_folder: tool.schema - .string() - .optional() - .describe('Optional: Project folder name in vault (auto-detects if omitted)'), - }, - async execute(args, _ctx) { - try { - const result = await mcpUpdateFrontmatter({ - path: args.path, - frontmatter: args.frontmatter, - mode: args.mode, - project_folder: args.project_folder, - }); - return result; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - return JSON.stringify({ success: false, error: message }, null, 2); - } - }, - }), - - // ==================== Replace Section Tool ==================== - replace_section: tool({ - description: - 'Replace a section in a markdown note by heading. Supports content-only (default), full, or heading-only replacement modes. Can create section if missing. Enforces read-before-write.', - args: { - path: tool.schema - .string() - .describe('Relative path to the note within the project folder'), - heading: tool.schema - .string() - .describe( - 'Heading text to find (without # symbols, e.g., "Installation" not "## Installation")' - ), - content: tool.schema.string().describe('New content for the section'), - mode: tool.schema - .enum(['content-only', 'full', 'heading-only']) - .optional() - .describe( - 'Replace mode: content-only (default, replace only section content), full (replace both heading and content), heading-only (replace only heading text, preserve content)' - ), - level: tool.schema - .number() - .int() - .min(1) - .max(6) - .optional() - .describe('Optional: Filter by heading level (1-6) to disambiguate duplicate headings'), - index: tool.schema - .number() - .int() - .min(0) - .optional() - .describe( - 'Optional: Which occurrence to replace if duplicates exist (0-based, use to disambiguate)' - ), - preview: tool.schema - .boolean() - .optional() - .describe( - 'Optional: Preview changes without applying them. Returns before/after comparison (default: false)' - ), - createIfMissing: tool.schema - .boolean() - .optional() - .describe( - 'Optional: Create section at end of file if not found. Only applies to content-only mode (default: false)' - ), - project_folder: tool.schema - .string() - .optional() - .describe('Optional: Project folder name in vault (auto-detects if omitted)'), - }, - async execute(args, _ctx) { - try { - const result = await mcpReplaceSection({ - path: args.path, - heading: args.heading, - content: args.content, - mode: args.mode as 'content-only' | 'full' | 'heading-only' | undefined, - level: args.level, - index: args.index, - preview: args.preview ?? false, - createIfMissing: args.createIfMissing ?? false, - project_folder: args.project_folder, - }); - return result; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - return JSON.stringify({ success: false, error: message }, null, 2); - } - }, - }), - - // ==================== Search Notes Tool ==================== - search_notes: tool({ - description: - 'Search for notes by content within the project folder. Returns matching files with snippets showing context around matches.', - args: { - query: tool.schema.string().describe('Search query text to find in note contents'), - case_sensitive: tool.schema - .boolean() - .optional() - .describe('Whether to perform case-sensitive search (default: false)'), - limit: tool.schema - .number() - .optional() - .describe('Maximum number of results to return (default: 10)'), - }, - async execute(args, _ctx) { - try { - const result = await mcpSearchNotes({ - query: args.query, - - case_sensitive: args.case_sensitive ?? false, - limit: args.limit ?? 10, - }); - return result; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - return JSON.stringify({ success: false, error: message }, null, 2); - } - }, - }), - - // ==================== Health Check Tool ==================== - health_check: tool({ - description: - 'Perform a comprehensive health check of the with-context-mcp environment. ' + - 'Validates environment variables, Obsidian API connection, and configuration. ' + - 'Returns detailed status and recommendations for fixing any issues. Fast (< 2 seconds).', - args: { - project_folder: tool.schema - .string() - .optional() - .describe('Optional: Project folder to check for .withcontextconfig.jsonc'), - }, - async execute(args, _ctx) { - try { - const result = await mcpHealthCheck({ - project_folder: args.project_folder, - }); - return JSON.stringify(result, null, 2); - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - return JSON.stringify({ success: false, error: message }, null, 2); - } - }, - }), - - // ==================== Set Project Context Tool ==================== - set_project_context: tool({ - description: - 'Set the project folder context for this session. All subsequent operations will use this folder unless overridden.', - args: { - project_folder: tool.schema - .string() - .describe('The project folder name within the vault (e.g., "my-web-app")'), - }, - async execute(args, _ctx) { - try { - const result = await mcpSetProjectContext({ - project_folder: args.project_folder, - }); - return result; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - return JSON.stringify({ success: false, error: message }, null, 2); - } - }, - }), - - // ==================== Get Note Metadata Tool ==================== - get_note_metadata: tool({ - description: - 'Get metadata about a note including word count, line count, frontmatter, tags, and headings.', - args: { - path: tool.schema - .string() - .describe('Relative path to the note within the project folder'), - }, - async execute(args, _ctx) { - try { - const result = await mcpGetNoteMetadata({ - path: args.path, - }); - return result; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - return JSON.stringify({ success: false, error: message }, null, 2); - } - }, - }), - - // ==================== Delete Note Tool ==================== - delete_note: tool({ - description: - 'Delete a markdown note from the project folder. Requires explicit confirmation. Lists file metadata (size, line count, preview) before deletion for safety verification.', - args: { - path: tool.schema.string().describe('Relative path to the note to delete'), - confirm: tool.schema.boolean().describe('Must be set to true to confirm deletion'), - }, - async execute(args, _ctx) { - try { - const result = await mcpDeleteNote({ - path: args.path, - confirm: args.confirm, - }); - return result; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - return JSON.stringify({ success: false, error: message }, null, 2); - } - }, - }), - - // ==================== Batch Write Notes Tool ==================== - batch_write_notes: tool({ - description: - 'Write multiple notes at once. Processes each note independently and returns a summary with per-note status.', - args: { - notes: tool.schema - .array( - tool.schema.object({ - path: tool.schema - .string() - .describe('Relative path to the note within the project folder'), - content: tool.schema.string().describe('Content to write to the note'), - mode: tool.schema - .enum(['create', 'overwrite', 'append']) - .optional() - .describe( - 'Write mode: create (fail if exists), overwrite (replace), or append (add to end). Default: overwrite' - ), - }) - ) - .describe('Array of notes to write'), - }, - async execute(args, _ctx) { - try { - const result = await mcpBatchWriteNotes({ - notes: args.notes.map((note) => ({ - path: note.path, - content: note.content, - mode: (note.mode || 'overwrite') as 'create' | 'overwrite' | 'append', - })), - }); - return result; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - return JSON.stringify({ success: false, error: message }, null, 2); - } - }, - }), - - // ==================== List Templates Tool ==================== - list_templates: tool({ - description: - 'List all available note templates with their descriptions and required variables.', - args: {}, - async execute(_args, _ctx) { - try { - const result = await mcpListTemplates({}); - return result; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - return JSON.stringify({ success: false, error: message }, null, 2); - } - }, - }), - - // ==================== Create From Template Tool ==================== - create_from_template: tool({ - description: - 'Create a new note from a template. Templates support variable substitution and auto-fill common variables like date and time.', - args: { - template_name: tool.schema.string().describe('Name of the template to use'), - filename: tool.schema - .string() - .describe('Filename for the new note (e.g., "CHANGELOG.md" or "docs/meeting.md")'), - variables: tool.schema - .record(tool.schema.string(), tool.schema.string()) - .optional() - .describe( - 'Variables to substitute in the template (e.g., {"version": "1.0.0", "author": "John"})' - ), - }, - async execute(args, _ctx) { - try { - const result = await mcpCreateFromTemplate({ - template_name: args.template_name, - filename: args.filename, - variables: args.variables, - }); - return result; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - return JSON.stringify({ success: false, error: message }, null, 2); - } - }, - }), - - // ==================== Ingest Notes Tool ==================== - ingest_notes: tool({ - description: - 'Ingest local documentation files to Obsidian vault. Scans the current project for documentation files based on .withcontextconfig.jsonc delegation rules and copies them to the vault.', - args: { - dry_run: tool.schema - .boolean() - .optional() - .describe('If true, show what would be ingested without actually writing files'), - delete_local_files: tool.schema - .boolean() - .optional() - .describe( - 'If true, delete local files after successful ingestion. Requires explicit confirmation.' - ), - force_delete: tool.schema - .boolean() - .optional() - .describe('If true, skip safety checks when deleting. Use with caution!'), - }, - async execute(args, _ctx) { - try { - const result = await mcpIngestNotes({ - dry_run: args.dry_run ?? false, - delete_local_files: args.delete_local_files ?? false, - force_delete: args.force_delete ?? false, - }); - return result; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - return JSON.stringify({ success: false, error: message }, null, 2); - } - }, - }), - - // ==================== Sync Notes Tool ==================== - sync_notes: tool({ - description: - 'Bidirectionally sync documentation files between local project and Obsidian vault. Files are synced based on .withcontextconfig.jsonc delegation rules and moved between locations (deleted from source after successful copy).', - args: { - dry_run: tool.schema - .boolean() - .optional() - .describe('If true, show what would be synced without actually moving files'), - }, - async execute(args, _ctx) { - try { - const result = await mcpSyncNotes({ - dry_run: args.dry_run ?? false, - }); - return result; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - return JSON.stringify({ success: false, error: message }, null, 2); - } - }, - }), - - // ==================== Teleport Notes Tool ==================== - teleport_notes: tool({ - description: - 'Teleport documentation files from Obsidian vault to local project. Downloads files from the vault back to the local project.', - args: { - dry_run: tool.schema - .boolean() - .optional() - .describe('If true, show what would be teleported without actually writing files'), - delete_from_vault: tool.schema - .boolean() - .optional() - .describe( - 'If true, delete files from vault after successful teleport. Use with caution!' - ), - force_delete: tool.schema - .boolean() - .optional() - .describe('If true, skip safety checks when deleting. Use with extreme caution!'), - }, - async execute(args, _ctx) { - try { - const result = await mcpTeleportNotes({ - dry_run: args.dry_run ?? false, - delete_from_vault: args.delete_from_vault ?? false, - force_delete: args.force_delete ?? false, - }); - return result; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - return JSON.stringify({ success: false, error: message }, null, 2); - } - }, - }), - - // ==================== Setup Notes Tool ==================== - setup_notes: tool({ - description: - 'Intelligent documentation setup for the project. Scans repository structure, analyzes documentation files, and generates smart delegation rules in .withcontextconfig.jsonc. Provides recommendations for vault vs local placement.', - args: { - project_root: tool.schema - .string() - .optional() - .describe('Project root directory (defaults to current working directory)'), - force: tool.schema.boolean().optional().describe('If true, overwrite existing config'), - create_structure: tool.schema - .boolean() - .optional() - .describe('If true, create recommended folder structure in vault (default: true)'), - project_folder: tool.schema - .string() - .optional() - .describe('Project folder name in vault (required if create_structure is true)'), - auto_apply: tool.schema - .boolean() - .optional() - .describe('If true, skip confirmation prompts'), - }, - async execute(args, _ctx) { - try { - const result = await mcpSetupNotes({ - project_root: args.project_root, - force: args.force ?? false, - create_structure: args.create_structure ?? true, - project_folder: args.project_folder, - auto_apply: args.auto_apply ?? false, - }); - return result; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - return JSON.stringify({ success: false, error: message }, null, 2); - } - }, - }), - - // ==================== Validate Config Tool ==================== - validate_config: tool({ - description: - 'Validate .withcontextconfig.jsonc for errors and warnings. Provides detailed feedback on configuration issues including schema validation, pattern conflicts, and best practice recommendations.', - args: { - project_root: tool.schema.string().describe('Project root directory'), - config_path: tool.schema - .string() - .optional() - .describe('Path to config file (defaults to .withcontextconfig.jsonc)'), - }, - async execute(args, _ctx) { - try { - const result = await mcpValidateConfig({ - project_root: args.project_root, - config_path: args.config_path, - }); - return result; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - return JSON.stringify({ success: false, error: message }, null, 2); - } - }, - }), - - // ==================== Preview Delegation Tool ==================== - preview_delegation: tool({ - description: - 'Preview which files will be delegated to vault vs kept local based on configuration patterns. Useful for testing patterns before syncing. Shows reasoning for decisions and allows filtering.', - args: { - project_root: tool.schema.string().describe('Project root directory'), - config_path: tool.schema - .string() - .optional() - .describe('Path to config file (defaults to .withcontextconfig.jsonc)'), - file_patterns: tool.schema - .array(tool.schema.string()) - .optional() - .describe('Glob patterns for files to preview (default: ["**/*.md"])'), - limit: tool.schema - .number() - .optional() - .describe('Maximum number of files to show per category (default: 100)'), - show_reasoning: tool.schema - .boolean() - .optional() - .describe('Show reasoning for each delegation decision (default: false)'), - vault_only: tool.schema.boolean().optional().describe('Show only files going to vault'), - local_only: tool.schema.boolean().optional().describe('Show only files staying local'), - specific_files: tool.schema - .array(tool.schema.string()) - .optional() - .describe('Preview delegation for specific files only'), - }, - async execute(args, _ctx) { - try { - const result = await mcpPreviewDelegation({ - project_root: args.project_root, - config_path: args.config_path, - file_patterns: args.file_patterns, - limit: args.limit ?? 100, - show_reasoning: args.show_reasoning ?? false, - vault_only: args.vault_only ?? false, - local_only: args.local_only ?? false, - specific_files: args.specific_files, - }); - return result; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - return JSON.stringify({ success: false, error: message }, null, 2); - } - }, - }), - - // ==================== Start Session Tool ==================== - start_session: tool({ - description: - 'Start a new development session for the project. Creates a new session and persists it to vault. Sets the project context for subsequent operations.', - args: { - project_folder: tool.schema - .string() - .describe('Project folder name in vault (e.g., "my-project")'), - message: tool.schema - .string() - .optional() - .describe('Optional message to describe session purpose'), - }, - async execute(args, _ctx) { - try { - const result = await mcpStartSession({ - project_folder: args.project_folder, - message: args.message, - }); - return result; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - return JSON.stringify({ success: false, error: message }, null, 2); - } - }, - }), - - // ==================== Pause Session Tool ==================== - pause_session: tool({ - description: - 'Pause the current active session. Saves session state to vault and clears timers. Session can be resumed later with resume_session.', - args: { - project_folder: tool.schema - .string() - .optional() - .describe( - 'Optional: Project folder name in vault (auto-detects from current context if omitted)' - ), - }, - async execute(args, _ctx) { - try { - const result = await mcpPauseSession({ - project_folder: args.project_folder, - }); - return result; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - return JSON.stringify({ success: false, error: message }, null, 2); - } - }, - }), - - // ==================== Resume Session Tool ==================== - resume_session: tool({ - description: - 'Resume a paused session. If session_id is provided, loads that specific session from vault. Otherwise, resumes the most recent paused session.', - args: { - project_folder: tool.schema - .string() - .optional() - .describe( - 'Optional: Project folder name in vault (auto-detects from current context if omitted)' - ), - session_id: tool.schema - .string() - .optional() - .describe('Optional session ID to resume (defaults to most recent paused session)'), - }, - async execute(args, _ctx) { - try { - const result = await mcpResumeSession({ - project_folder: args.project_folder, - session_id: args.session_id, - }); - return result; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - return JSON.stringify({ success: false, error: message }, null, 2); - } - }, - }), - - // ==================== End Session Tool ==================== - end_session: tool({ - description: - 'Complete and archive the current session. Marks session as completed, saves final state to vault archive, and clears active session. Generates comprehensive summary.', - args: { - project_folder: tool.schema - .string() - .optional() - .describe( - 'Optional: Project folder name in vault (auto-detects from current context if omitted)' - ), - message: tool.schema - .string() - .optional() - .describe('Optional completion message or summary'), - }, - async execute(args, _ctx) { - try { - const result = await mcpEndSession({ - project_folder: args.project_folder, - message: args.message, - }); - return result; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - return JSON.stringify({ success: false, error: message }, null, 2); - } - }, - }), - - // ==================== Get Session Status Tool ==================== - get_session_status: tool({ - description: - 'Get current session status and comprehensive details. Returns session information including ID, status, duration, files tracked, todos, changelog entries, git context, and metadata.', - args: { - project_folder: tool.schema - .string() - .optional() - .describe('Optional: Project folder name in vault (auto-detects if omitted)'), - }, - async execute(args, _ctx) { - try { - const result = await mcpGetSessionStatus({ - project_folder: args.project_folder, - }); - return result; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - return JSON.stringify({ success: false, error: message }, null, 2); - } - }, - }), - - // ==================== Add Changelog Entry Tool ==================== - add_changelog_entry: tool({ - description: - 'Add a changelog entry to the current session. User provides type and message for semi-automatic tracking. Entry is immediately persisted to session state.', - args: { - project_folder: tool.schema - .string() - .optional() - .describe( - 'Optional: Project folder name in vault (auto-detects from current context if omitted)' - ), - type: tool.schema - .enum(['feature', 'fix', 'refactor', 'docs', 'test', 'chore']) - .describe('Type of change (conventional commit type)'), - message: tool.schema.string().describe('Description of the change'), - files: tool.schema - .array(tool.schema.string()) - .optional() - .describe('Optional: Files affected by this change'), - breaking: tool.schema - .boolean() - .optional() - .describe('Optional: Whether this is a breaking change'), - }, - async execute(args, _ctx) { - try { - const result = await mcpAddChangelogEntry({ - project_folder: args.project_folder, - type: args.type as 'feature' | 'fix' | 'refactor' | 'docs' | 'test' | 'chore', - message: args.message, - files: args.files, - breaking: args.breaking, - }); - return result; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - return JSON.stringify({ success: false, error: message }, null, 2); - } - }, - }), - - // ==================== Get Session Changelog Tool ==================== - get_session_changelog: tool({ - description: - 'View changelog for the current session. Returns all entries grouped by type with file counts and breaking change indicators.', - args: { - project_folder: tool.schema - .string() - .optional() - .describe('Optional: Project folder name in vault (auto-detects if omitted)'), - }, - async execute(args, _ctx) { - try { - const result = await mcpGetSessionChangelog({ - project_folder: args.project_folder, - }); - return result; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - return JSON.stringify({ success: false, error: message }, null, 2); - } - }, - }), - - // ==================== Get Commit Suggestion Tool ==================== - get_commit_suggestion: tool({ - description: - 'Generate a conventional commit message from session changelog. Analyzes entries to determine primary type and formats message. Includes all changes as bullet points and detects breaking changes.', - args: { - project_folder: tool.schema - .string() - .optional() - .describe('Optional: Project folder name in vault (auto-detects if omitted)'), - conventional: tool.schema - .boolean() - .optional() - .describe('Use conventional commit format (default: true)'), - }, - async execute(args, _ctx) { - try { - const result = await mcpGetCommitSuggestion({ - project_folder: args.project_folder, - conventional: args.conventional ?? true, - }); - return result; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - return JSON.stringify({ success: false, error: message }, null, 2); - } - }, - }), - - // ==================== Add Todo Tool ==================== - add_todo: tool({ - description: - 'Add a todo to the current session. Todos persist across sessions and are tracked per project. Immediately saved to session state in vault.', - args: { - project_folder: tool.schema - .string() - .optional() - .describe( - 'Optional: Project folder name in vault (auto-detects from current context if omitted)' - ), - content: tool.schema.string().describe('Todo content/description'), - priority: tool.schema - .enum(['high', 'medium', 'low']) - .optional() - .describe('Priority level (default: medium)'), - }, - async execute(args, _ctx) { - try { - const result = await mcpAddTodo({ - project_folder: args.project_folder, - content: args.content, - priority: (args.priority as 'high' | 'medium' | 'low') ?? 'medium', - }); - return result; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - return JSON.stringify({ success: false, error: message }, null, 2); - } - }, - }), - - // ==================== Update Todo Tool ==================== - update_todo: tool({ - description: - 'Update todo status or priority. Automatically sets completedAt timestamp when marked as completed. Changes are immediately persisted to vault.', - args: { - project_folder: tool.schema - .string() - .optional() - .describe( - 'Optional: Project folder name in vault (auto-detects from current context if omitted)' - ), - todo_id: tool.schema.string().describe('Todo ID to update'), - status: tool.schema - .enum(['pending', 'in_progress', 'completed', 'cancelled']) - .optional() - .describe('New status'), - priority: tool.schema.enum(['high', 'medium', 'low']).optional().describe('New priority'), - }, - async execute(args, _ctx) { - try { - const result = await mcpUpdateTodo({ - project_folder: args.project_folder, - todo_id: args.todo_id, - status: args.status as - | 'pending' - | 'in_progress' - | 'completed' - | 'cancelled' - | undefined, - priority: args.priority as 'high' | 'medium' | 'low' | undefined, - }); - return result; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - return JSON.stringify({ success: false, error: message }, null, 2); - } - }, - }), - - // ==================== List Todos Tool ==================== - list_todos: tool({ - description: - 'List todos from current session with optional filters. Returns todos grouped by status with counts and priority indicators. Can filter by status and/or priority.', - args: { - project_folder: tool.schema - .string() - .optional() - .describe('Optional: Project folder name in vault (auto-detects if omitted)'), - status: tool.schema - .enum(['pending', 'in_progress', 'completed', 'cancelled']) - .optional() - .describe('Filter by status'), - priority: tool.schema - .enum(['high', 'medium', 'low']) - .optional() - .describe('Filter by priority'), - }, - async execute(args, _ctx) { - try { - const result = await mcpListTodos({ - project_folder: args.project_folder, - status: args.status as - | 'pending' - | 'in_progress' - | 'completed' - | 'cancelled' - | undefined, - priority: args.priority as 'high' | 'medium' | 'low' | undefined, - }); - return result; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - return JSON.stringify({ success: false, error: message }, null, 2); - } - }, - }), - - // ==================== Analyze Vault Structure Tool ==================== - analyze_vault_structure: tool({ - description: - 'Analyze vault structure and content. Scans all markdown files, extracts metadata (headings, frontmatter, links, tags), categorizes files, identifies orphans, and builds comprehensive statistics.', - args: { - exclude_patterns: tool.schema - .array(tool.schema.string()) - .optional() - .describe( - 'Optional: Glob patterns to exclude from analysis (e.g., ["*.tmp", "drafts/*"])' - ), - include_categories: tool.schema - .boolean() - .optional() - .describe( - 'Optional: Whether to include category statistics in results (default: true)' - ), - include_orphans: tool.schema - .boolean() - .optional() - .describe( - 'Optional: Whether to identify orphan files (no incoming/outgoing links, default: true)' - ), - max_file_size_mb: tool.schema - .number() - .optional() - .describe('Optional: Maximum file size in MB to analyze (default: 10)'), - max_files: tool.schema - .number() - .optional() - .describe('Optional: Maximum number of files to analyze (for limiting large vaults)'), - }, - async execute(args, _ctx) { - try { - const result = await mcpAnalyzeVaultStructure({ - exclude_patterns: args.exclude_patterns, - include_categories: args.include_categories ?? true, - include_orphans: args.include_orphans ?? true, - max_file_size_mb: args.max_file_size_mb ?? 10, - max_files: args.max_files, - }); - return result; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - return JSON.stringify({ success: false, error: message }, null, 2); - } - }, - }), - - // ==================== Reorganize Vault Tool ==================== - reorganize_vault: tool({ - description: - 'Execute vault reorganization based on an organization plan. Supports move and rename operations with dry-run mode (enabled by default), automatic link updates, rollback on failure, and confidence-based filtering.', - args: { - plan: tool.schema - .object({ - suggestions: tool.schema - .array( - tool.schema.object({ - type: tool.schema - .enum(['rename', 'move', 'both']) - .describe('Type of reorganization operation'), - currentPath: tool.schema - .string() - .describe('Current file path relative to vault root'), - suggestedPath: tool.schema - .string() - .optional() - .describe('Suggested new path (for move operations)'), - suggestedName: tool.schema - .string() - .optional() - .describe('Suggested new name (for rename operations)'), - reason: tool.schema - .string() - .describe('Human-readable reason for this suggestion'), - confidence: tool.schema.number().describe('Confidence score (0-1)'), - impact: tool.schema - .object({ - affectedFiles: tool.schema.number().optional(), - linksToUpdate: tool.schema.number().optional(), - potentialBrokenLinks: tool.schema.array(tool.schema.string()).optional(), - complexity: tool.schema.enum(['low', 'medium', 'high']).optional(), - }) - .optional() - .describe('Impact assessment for this operation'), - targetCategory: tool.schema - .string() - .optional() - .describe('Target category after reorganization'), - }) - ) - .describe('List of reorganization suggestions'), - estimatedImpact: tool.schema - .object({ - filesToMove: tool.schema.number().optional(), - filesToRename: tool.schema.number().optional(), - linksToUpdate: tool.schema.number().optional(), - filesRequiringLinkUpdates: tool.schema.number().optional(), - estimatedDuration: tool.schema.number().optional(), - hasRiskyOperations: tool.schema.boolean().optional(), - }) - .optional() - .describe('Overall estimated impact of executing the plan'), - warnings: tool.schema - .array( - tool.schema.object({ - severity: tool.schema.enum(['info', 'warning', 'error']), - filePath: tool.schema.string().optional(), - message: tool.schema.string(), - suggestion: tool.schema.string().optional(), - }) - ) - .optional() - .describe('List of warnings about the plan'), - summary: tool.schema.string().optional().describe('High-level summary of the plan'), - requiresManualReview: tool.schema.boolean().optional(), - }) - .describe('The reorganization plan to execute (from analyze_vault_structure)'), - dry_run: tool.schema - .boolean() - .optional() - .describe( - 'If true, preview operations without making changes (default: true for safety)' - ), - update_links: tool.schema - .boolean() - .optional() - .describe('If true, automatically update links in other files (default: true)'), - create_backup: tool.schema - .boolean() - .optional() - .describe('If true, create backups before executing (default: true for safety)'), - min_confidence: tool.schema - .number() - .optional() - .describe('Minimum confidence threshold for executing operations (default: 0.7)'), - }, - async execute(args, _ctx) { - try { - const result = await mcpReorganizeNotes({ - plan: args.plan as unknown as Parameters[0]['plan'], - dry_run: args.dry_run ?? true, - update_links: args.update_links ?? true, - create_backup: args.create_backup ?? true, - min_confidence: args.min_confidence ?? 0.7, - }); - return result; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - return JSON.stringify({ success: false, error: message }, null, 2); - } - }, - }), - generate_organization_plan: tool({ - description: - 'Generate an organization plan using a preset strategy. Analyzes vault structure and applies preset rules to create comprehensive reorganization suggestions. Use this BEFORE reorganize_notes.', - args: { - preset_id: tool.schema - .string() - .describe( - 'Preset to apply: "clean", "minimal", "docs-as-code", or "research". Use list_presets for details.' - ), - min_confidence: tool.schema - .number() - .optional() - .describe('Minimum confidence threshold for including suggestions (default: 0.7)'), - exclude_files: tool.schema - .array(tool.schema.string()) - .optional() - .describe('File patterns to exclude from analysis (glob patterns)'), - custom_rules: tool.schema - .array( - tool.schema.object({ - name: tool.schema.string(), - priority: tool.schema.number(), - pattern: tool.schema.string(), - targetPath: tool.schema.string(), - confidence: tool.schema.number(), - reason: tool.schema.string(), - }) - ) - .optional() - .describe('Additional custom rules to apply after preset rules'), - max_file_size_mb: tool.schema - .number() - .optional() - .describe('Maximum file size to analyze in MB (default: 10)'), - }, - async execute(args, _ctx) { - try { - const result = await mcpGenerateOrganizationPlan({ - preset_id: args.preset_id, - min_confidence: args.min_confidence ?? 0.7, - exclude_files: args.exclude_files, - custom_rules: args.custom_rules as - | Array<{ - name: string; - priority: number; - pattern: string; - targetPath: string; - confidence: number; - reason: string; - }> - | undefined, - max_file_size_mb: args.max_file_size_mb ?? 10, - }); - return result; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - return JSON.stringify({ success: false, error: message }, null, 2); - } - }, - }), - }, - }; -}; - -// Default export for plugin loading -export default WithContextPlugin; diff --git a/.config/opencode/command/impeccable.md b/.config/opencode/command/impeccable.md new file mode 100644 index 0000000..33f4a6f --- /dev/null +++ b/.config/opencode/command/impeccable.md @@ -0,0 +1,6 @@ +--- +description: Use when the user wants to design, redesign, shape, critique, audit, polish, clarify, distill, harden, optimize, adapt, animate, colorize, extract, or otherwise improve a frontend interface. Covers websites, landing pages, dashboards, product UI, app shells, components, forms, settings, onboarding, and empty states. Handles UX review, visual hierarchy, information architecture, cognitive load, accessibility, performance, responsive behavior, theming, anti-patterns, typography, fonts, spacing, layout, alignment, color, motion, micro-interactions, UX copy, error states, edge cases, i18n, and reusable design systems or tokens. Also use for bland designs that need to become bolder or more delightful, loud designs that should become quieter, live browser iteration on UI elements, or ambitious visual effects that should feel technically extraordinary. Not for backend-only or non-UI tasks. +agent: build +subtask: true +--- +Call skill({ name: "impeccable" }) and follow its `Setup` and `Commands` sections to handle $ARGUMENTS. diff --git a/.config/opencode/opencode.json b/.config/opencode/opencode.json index 1c60695..c2b4942 100644 --- a/.config/opencode/opencode.json +++ b/.config/opencode/opencode.json @@ -1,124 +1,457 @@ { - "$schema": "https://opencode.ai/config.json", - "autoupdate": true, - "share": "disabled", - "disabled_providers": [ - "openai" - ], - "plugin": [ - "oh-my-opencode" - ], - "instructions": [ - "docs/guidelines.md", - "docs/development-standards.md", - "test/testing-guidelines.md", - "packages/*/AGENTS.md" - ], - "agent": { - "build": { - "mode": "all" - }, - "plan": { - "mode": "primary" - }, - "setup": { - "mode": "subagent", - "description": "Generates project-specific documentation and guidelines through interactive discovery" - }, - "review": { - "mode": "subagent", - "description": "Reviews code for best practices and potential issues" - }, - "test": { - "mode": "subagent", - "description": "Runs tests and analyzes test results" - }, - "docs": { - "mode": "subagent", - "description": "Generates and maintains documentation" - }, - "refactor": { - "mode": "subagent", - "description": "Refactors code for better quality and maintainability" - }, - "debug": { - "mode": "subagent", - "description": "Debugs issues and troubleshoots problems" - }, - "security": { - "mode": "subagent", - "description": "Audits code for security vulnerabilities" - }, - "webgen": { - "mode": "subagent", - "description": "Generates complete web pages from ideas using any framework" - } + "$schema": "https://opencode.ai/config.json", + "autoupdate": true, + "share": "disabled", + "model": "openai/gpt-6-astra", + "small_model": "zai-coding-plan/glm-5.3-flash", + "instructions": [ + "~/.config/opencode/DEVELOPMENT-WORKFLOW.md", + "~/.config/opencode/UI-WORKFLOW.md" + ], + "enabled_providers": [ + "openai", + "google", + "zai-coding-plan" + ], + "plugin": [ + "opencode-antigravity-auth@latest", + "envsitter-guard", + "oh-my-openagent@latest" + ], + "agent": { + "build": { + "mode": "all", + "model": "openai/gpt-6-astra" }, - "mcp": { - "time": { - "type": "local", - "command": [ - "/Users/davidibia/.local/bin/uvx", - "mcp-server-time", - "--local-timezone=Africa/Lagos" - ], - "enabled": true - }, - "shadcn": { - "type": "local", - "command": [ - "npx", - "shadcn@latest", - "mcp" - ], - "enabled": false - }, - "astro": { - "type": "remote", - "url": "https://mcp.docs.astro.build/mcp", - "enabled": false - }, - "context7": { - "type": "remote", - "url": "https://mcp.context7.com/mcp", - "headers": { - "CONTEXT7_API_KEY": "env:CONTEXT7_API_KEY" - }, - "enabled": false - }, - "ast-grep": { - "type": "local", - "command": [ - "uv", - "--directory", - "/Users/davidibia/Projects/MCP/ast-grep-mcp", - "run", - "main.py" - ], - "enabled": true - }, - "chrome-devtools": { - "type": "local", - "command": [ - "npx", - "chrome-devtools-mcp@latest" - ], - "enabled": false - }, - "with-context": { - "type": "local", - "command": [ - "npx", - "-y", - "with-context-mcp" - ], - "environment": { - "OBSIDIAN_API_KEY": "env:OBSIDIAN_API_KEY", - "OBSIDIAN_API_URL": "env:OBSIDIAN_API_URL", - "OBSIDIAN_VAULT": "env:OBSIDIAN_VAULT", - "PROJECT_BASE_PATH": "env:PROJECT_BASE_PATH" - }, - "enabled": false - } + "plan": { + "mode": "primary", + "model": "zai-coding-plan/glm-5.3" + }, + "setup": { + "mode": "subagent", + "description": "Generates project-specific documentation and guidelines through interactive discovery" + }, + "review": { + "mode": "subagent", + "description": "Reviews code for best practices and potential issues", + "model": "zai-coding-plan/glm-5.3" + }, + "test": { + "mode": "subagent", + "description": "Runs tests and analyzes test results", + "model": "openai/gpt-6-astra" + }, + "docs": { + "mode": "subagent", + "description": "Generates and maintains documentation" + }, + "refactor": { + "mode": "subagent", + "description": "Refactors code for better quality and maintainability" + }, + "debug": { + "mode": "subagent", + "description": "Debugs issues and troubleshoots problems" + }, + "security": { + "mode": "subagent", + "description": "Audits code for security vulnerabilities", + "model": "zai-coding-plan/glm-5.3" + }, + "webgen": { + "mode": "subagent", + "description": "Generates complete web pages from ideas using any framework" } + }, + "mcp": { + "playwright": { + "type": "local", + "command": [ + "npx", + "-y", + "@playwright/mcp@0.0.81", + "--isolated", + "--headless", + "--browser", + "chrome", + "--image-responses", + "allow" + ], + "enabled": true + }, + "headroom": { + "type": "local", + "command": [ + "{env:HOME}/.local/bin/headroom", + "mcp", + "serve" + ], + "enabled": true + }, + "agcanvas": { + "type": "local", + "command": [ + "{env:HOME}/.local/bin/agcanvas-mcp", + "--port", + "9876" + ], + "enabled": true + }, + "time": { + "type": "local", + "command": [ + "{env:HOME}/.local/bin/uvx", + "mcp-server-time", + "--local-timezone=Africa/Lagos" + ], + "enabled": true + }, + "ast-grep": { + "type": "local", + "command": [ + "uv", + "--directory", + "{env:HOME}/Software/Projects/MCP/ast-grep-mcp", + "run", + "main.py" + ], + "enabled": true + }, + "chrome-devtools": { + "type": "local", + "command": [ + "npx", + "chrome-devtools-mcp@latest" + ], + "enabled": true + }, + "portfolio-prod": { + "type": "local", + "command": [ + "node", + "{env:HOME}/.mcp-servers/portfolio-mcp/dist/index.js" + ], + "environment": { + "PORTFOLIO_API_URL": "https://boxpositron.dev", + "PORTFOLIO_API_KEY": "env:PORTFOLIO_API_KEY" + }, + "enabled": true + }, + "linear": { + "type": "remote", + "url": "https://mcp.linear.app/mcp", + "enabled": true + }, + "reoclo": { + "type": "local", + "command": [ + "reoclo", + "mcp" + ], + "enabled": true + } + }, + "provider": { + "google": { + "name": "Google", + "models": { + "antigravity-gemini-3.1-pro": { + "name": "Gemini 3.1 Pro (Antigravity)", + "limit": { + "context": 1048576, + "output": 65535 + }, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "variants": { + "low": { + "thinkingLevel": "low" + }, + "high": { + "thinkingLevel": "high" + } + } + }, + "antigravity-gemini-3-pro": { + "name": "Gemini 3 Pro (Antigravity)", + "limit": { + "context": 1048576, + "output": 65535 + }, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "variants": { + "low": { + "thinkingLevel": "low" + }, + "high": { + "thinkingLevel": "high" + } + } + }, + "antigravity-gemini-3-flash": { + "name": "Gemini 3 Flash (Antigravity)", + "limit": { + "context": 1048576, + "output": 65536 + }, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "variants": { + "minimal": { + "thinkingLevel": "minimal" + }, + "low": { + "thinkingLevel": "low" + }, + "medium": { + "thinkingLevel": "medium" + }, + "high": { + "thinkingLevel": "high" + } + } + }, + "antigravity-claude-sonnet-4-6": { + "name": "Claude Sonnet 4.6 (Antigravity)", + "limit": { + "context": 200000, + "output": 64000 + }, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + } + }, + "antigravity-claude-sonnet-4-6-thinking": { + "name": "Claude Sonnet 4.6 Thinking (Antigravity)", + "limit": { + "context": 200000, + "output": 64000 + }, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "variants": { + "low": { + "thinkingConfig": { + "thinkingBudget": 8192 + } + }, + "max": { + "thinkingConfig": { + "thinkingBudget": 32768 + } + } + } + }, + "antigravity-claude-opus-4-5-thinking": { + "name": "Claude Opus 4.5 Thinking (Antigravity)", + "limit": { + "context": 200000, + "output": 64000 + }, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "variants": { + "low": { + "thinkingConfig": { + "thinkingBudget": 8192 + } + }, + "max": { + "thinkingConfig": { + "thinkingBudget": 32768 + } + } + } + } + } + }, + "openai": { + "name": "OpenAI", + "blacklist": [ + "gpt-5.2-codex", + "gpt-5.1-codex-max" + ], + "options": { + "reasoningEffort": "high", + "reasoningSummary": "auto", + "textVerbosity": "medium", + "include": [ + "reasoning.encrypted_content" + ], + "store": false + }, + "models": { + "gpt-5.2": { + "name": "GPT 5.2 (OAuth)", + "limit": { + "context": 272000, + "output": 128000 + }, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "variants": { + "none": { + "reasoningEffort": "none", + "reasoningSummary": "auto", + "textVerbosity": "medium" + }, + "low": { + "reasoningEffort": "low", + "reasoningSummary": "auto", + "textVerbosity": "medium" + }, + "medium": { + "reasoningEffort": "medium", + "reasoningSummary": "auto", + "textVerbosity": "medium" + }, + "high": { + "reasoningEffort": "high", + "reasoningSummary": "detailed", + "textVerbosity": "medium" + }, + "xhigh": { + "reasoningEffort": "xhigh", + "reasoningSummary": "detailed", + "textVerbosity": "medium" + } + } + }, + "gpt-5.2-codex": { + "name": "GPT 5.2 Codex (OAuth)", + "limit": { + "context": 272000, + "output": 128000 + }, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "variants": { + "low": { + "reasoningEffort": "low", + "reasoningSummary": "auto", + "textVerbosity": "medium" + }, + "medium": { + "reasoningEffort": "medium", + "reasoningSummary": "auto", + "textVerbosity": "medium" + }, + "high": { + "reasoningEffort": "high", + "reasoningSummary": "detailed", + "textVerbosity": "medium" + }, + "xhigh": { + "reasoningEffort": "xhigh", + "reasoningSummary": "detailed", + "textVerbosity": "medium" + } + } + }, + "gpt-5.1-codex-max": { + "name": "GPT 5.1 Codex Max (OAuth)", + "limit": { + "context": 272000, + "output": 128000 + }, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "variants": { + "low": { + "reasoningEffort": "low", + "reasoningSummary": "detailed", + "textVerbosity": "medium" + }, + "medium": { + "reasoningEffort": "medium", + "reasoningSummary": "detailed", + "textVerbosity": "medium" + }, + "high": { + "reasoningEffort": "high", + "reasoningSummary": "detailed", + "textVerbosity": "medium" + }, + "xhigh": { + "reasoningEffort": "xhigh", + "reasoningSummary": "detailed", + "textVerbosity": "medium" + } + } + } + } + } + }, + "disabled_providers": [ + "nine-router" + ] } diff --git a/.config/opencode/plugin/lib/claude-only-command-registry.ts b/.config/opencode/plugin/lib/claude-only-command-registry.ts new file mode 100644 index 0000000..c87e664 --- /dev/null +++ b/.config/opencode/plugin/lib/claude-only-command-registry.ts @@ -0,0 +1,55 @@ +type PrefixRule = { + readonly kind: "prefix" + readonly prefix: string + readonly reason: string +} + +type ExactRule = { + readonly kind: "exact" + readonly name: string + readonly reason: string +} + +export type ClaudeOnlyCommandRule = PrefixRule | ExactRule + +export const CLAUDE_ONLY_COMMAND_RULES: readonly ClaudeOnlyCommandRule[] = [ + { + kind: "prefix", + prefix: "oh-my-claudecode:", + reason: "Operates the Oh My ClaudeCode runtime and workflows.", + }, + { + kind: "prefix", + prefix: "claude-notifications-go:", + reason: "Configures notification hooks, binaries, and files for Claude Code.", + }, + { + kind: "exact", + name: "remember:doctor", + reason: "Executes diagnostics through CLAUDE_PLUGIN_ROOT; remember:remember stays because it supports multiple agent hosts.", + }, + { + kind: "exact", + name: "mcp-server-dev:build-mcpb", + reason: "Produces Anthropic MCPB bundles with claude_desktop compatibility; the server and app builders stay because they target multiple MCP hosts.", + }, +] as const + +function matchesRule(name: string, rule: ClaudeOnlyCommandRule): boolean { + switch (rule.kind) { + case "prefix": + return name.startsWith(rule.prefix) + case "exact": + return name === rule.name + default: + return assertNever(rule) + } +} + +function assertNever(value: never): never { + throw new Error(`Unhandled rule: ${JSON.stringify(value)}`) +} + +export function isClaudeOnlyCommand(name: string): boolean { + return CLAUDE_ONLY_COMMAND_RULES.some((rule) => matchesRule(name, rule)) +} diff --git a/.config/opencode/plugin/package.json b/.config/opencode/plugin/package.json deleted file mode 100644 index 653c6ec..0000000 --- a/.config/opencode/plugin/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "opencode-plugins", - "version": "1.0.0", - "description": "OpenCode plugins for dotfiles", - "dependencies": { - "@opencode-ai/plugin": "^1.0.51" - } -} diff --git a/.config/opencode/plugin/strip-claude-only-commands.ts b/.config/opencode/plugin/strip-claude-only-commands.ts new file mode 100644 index 0000000..56c4d43 --- /dev/null +++ b/.config/opencode/plugin/strip-claude-only-commands.ts @@ -0,0 +1,15 @@ +import type { Plugin } from "@opencode-ai/plugin" + +import { isClaudeOnlyCommand } from "./lib/claude-only-command-registry.ts" + +export const StripClaudeOnlyCommandsPlugin: Plugin = async () => ({ + config: async (config) => { + if (!config.command) return + + for (const name of Object.keys(config.command)) { + if (isClaudeOnlyCommand(name)) { + delete config.command[name] + } + } + }, +}) diff --git a/.config/opencode/plugin/strip-omc-mcp.ts b/.config/opencode/plugin/strip-omc-mcp.ts new file mode 100644 index 0000000..8638e7c --- /dev/null +++ b/.config/opencode/plugin/strip-omc-mcp.ts @@ -0,0 +1,15 @@ +import type { Plugin } from "@opencode-ai/plugin" + +const OMC_MCP_PREFIX = "oh-my-claudecode:" + +export const StripOmcMcpPlugin: Plugin = async () => ({ + config: async (config) => { + if (!config.mcp) return + + for (const name of Object.keys(config.mcp)) { + if (name.startsWith(OMC_MCP_PREFIX)) { + delete config.mcp[name] + } + } + }, +}) diff --git a/.config/opencode/plugin/terminal-bell.ts b/.config/opencode/plugin/terminal-bell.ts deleted file mode 100644 index 0d48e9a..0000000 --- a/.config/opencode/plugin/terminal-bell.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { Plugin } from "@opencode-ai/plugin" - -export const NotificationPlugin: Plugin = async ({ project, client, $, directory, worktree }) => { - return { - event: async ({ event }) => { - // Send notification on session completion - if (event.type === "session.idle") { - await $`osascript -e 'display notification "Session completed!" with title "opencode"'` - } - }, - } -} diff --git a/.config/opencode/plugin/websearch.ts b/.config/opencode/plugin/websearch.ts deleted file mode 100644 index 653c1e1..0000000 --- a/.config/opencode/plugin/websearch.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { type Plugin, tool } from "@opencode-ai/plugin"; - -export const PerplexitySearch: Plugin = async (ctx) => { - return { - tool: { - search: tool({ - description: - "Run a quick search query to search the internet for results using Perplexity AI. Use this for quick lookups, latest information, and documentation.", - args: { - query: tool.schema.string().describe("Search query to execute"), - }, - execute: async (args) => { - const url = "https://api.perplexity.ai/chat/completions"; - const response = await fetch(url, { - method: "POST", - headers: { - Authorization: "Bearer " + process.env.PERPLEXITY_API_KEY, - "Content-Type": "application/json", - }, - body: JSON.stringify({ - model: "sonar", - messages: [ - { - role: "user", - content: args.query, - }, - ], - }), - }); - const data = await response.json(); - return JSON.stringify({ - answer: data.choices?.[0]?.message?.content, - citations: data.citations || [], - sources: data.search_results || [], - }); - }, - }), - - deepResearch: tool({ - description: - "Conduct an in-depth research using Perplexity's deep research model. Use this for exhaustive research, detailed analysis, comprehensive reports, or complex multi-source synthesis.", - args: { - query: tool.schema.string().describe("Research query or topic for exhaustive analysis"), - }, - execute: async (args) => { - const url = "https://api.perplexity.ai/chat/completions"; - const response = await fetch(url, { - method: "POST", - headers: { - Authorization: "Bearer " + process.env.PERPLEXITY_API_KEY, - "Content-Type": "application/json", - }, - body: JSON.stringify({ - model: "sonar-deep-research", - messages: [ - { - role: "user", - content: args.query, - }, - ], - }), - }); - const data = await response.json(); - return JSON.stringify({ - report: data.choices?.[0]?.message?.content, - citations: data.citations || [], - sources: data.search_results || [], - }); - }, - }), - }, - }; -}; diff --git a/.config/opencode/plugin/with-context.ts b/.config/opencode/plugin/with-context.ts deleted file mode 120000 index 95e27f0..0000000 --- a/.config/opencode/plugin/with-context.ts +++ /dev/null @@ -1 +0,0 @@ -/Users/davidibia/Projects/MCP/with-context-mcp/plugin/with-context.ts \ No newline at end of file diff --git a/.config/opencode/skills/frontend-workflow/SKILL.md b/.config/opencode/skills/frontend-workflow/SKILL.md new file mode 100644 index 0000000..1d74849 --- /dev/null +++ b/.config/opencode/skills/frontend-workflow/SKILL.md @@ -0,0 +1,50 @@ +--- +name: frontend-workflow +description: Use for substantive frontend UI implementation, redesign, styling, or visual review. Coordinates project-specific design direction, single-writer GPT implementation, browser image feedback, accessibility checks, and bounded correction. Not for backend-only work or trivial copy edits. +--- + +# Intentional frontend workflow + +## 1. Establish the contract + +Identify the page's audience, purpose and primary user action. Read existing components, tokens, PRODUCT.md/DESIGN.md and supplied references. Preserve established conventions unless a redesign is requested. Missing DESIGN.md does not mean missing design evidence. Record proposed defaults as proposals, not approved brand decisions. Ask only for a consequential unresolved choice, unavailable credential, or unsupported capability. + +For this dotfiles repository's demonstration surface, read `references/dotfiles-workbench.md`. For other products, use their own evidence, never inherit this demonstration's palette or audience. + +Write a short working design contract: typography and hierarchy, colour roles, spacing/layout rhythm, reusable components and states, imagery purpose, responsive behaviour, and measurable acceptance criteria. A resolved existing brief is sufficient; do not create unnecessary plans or research rounds. + +## 2. Load complementary guidance on demand + +- Load `impeccable` for direction/implementation, then only its relevant playbook. Resolve launcher commands from the loaded skill's base directory, not an assumed project-relative path. If its launcher cannot run, follow its documented fallback and read context directly. +- Load `web-design-guidelines` during accessibility and interaction review; fetch its current rule source and record the source/date. Network failure is a disclosed gap, not permission to invent a successful audit. +- Load the browser skill when using its tools. Prefer the configured isolated `playwright` MCP; preserve any existing working browser. Do not take over or kill the user's browser to resolve a profile lock. +- Optional design advice is subordinate to user instructions and product conventions. No blanket font/gradient/card/animation bans. No decoration just to look distinctive. Do not stack competing aesthetic skill libraries without a specific need. + +## 3. One owner, separate passes + +Use OMO's existing `visual-engineering` category and existing GPT model for implementation. Browser inspection and visual critique can be sequential passes by that same worker. No new model or agent is required. The registered GLM `multimodal-looker` is not the default visual-quality judge for this GPT workflow. + +Delegation handoff must include: purpose/action; context and reference paths; approved versus proposed decisions; acceptance criteria; run/build commands and URL; owned files; phase (`implement`, `inspect`, `critique`, or `correct`); remaining pass/time budget. Pass relevant skill names through the task tool's supported `load_skills`, not an invented category `skills` config key. One writer per file set. Review delegates are read-only and return findings; only the owner applies corrections after review completes. + +## 4. Build, then obtain actual visual evidence + +Run the affected build/type/tests. Start the app on loopback using its documented command. Inspect at desktop and mobile widths, with an intermediate width when breakpoints or layout complexity warrant it. Use product targets, or proposed defaults 1280×900, 390×844, and 768×900. Record URL, viewport, state, and screenshot path. + +After capture, check whether the tool response contains an actual image. If it returns only a path, use the native image-capable `read` tool to open the PNG, or attach it to the GPT session (`opencode run --model --file -- `). Keep existing model identifiers; do not invent one. Describe concrete visible details to demonstrate receipt. DOM checks are complementary, not substitutes. If the model cannot see pixels, report the exact failing delivery step and do not claim visual QA. + +## 5. Bounded critique and correction + +Normally allow up to three total inspect/correct passes. For each pass: +1. Inspect desktop/mobile screenshots and relevant states. Describe the observed hierarchy, typography, spacing, colour, and alignment. +2. Rank the three most significant visible problems, or fewer if fewer are material. Cite the viewport/state and user impact, not subjective novelty. Do not fabricate findings to reach three. +3. Correct those issues in one edit batch, rebuild, and capture/read fresh screenshots. Re-test affected interactions. + +Exercise the primary interaction and relevant loading, empty, error, focus and disabled states. Check horizontal overflow, wrapping/long content, text and control contrast, keyboard order/access, accessible labels/status messages, and reduced-motion behaviour. Record not-applicable states with a reason. Separate source review from browser-observed results; automated checks cannot establish aesthetic quality or complete accessibility conformance. + +Stop early when the acceptance criteria hold. At the cap, report residual issues and their impact instead of continuing cosmetic churn. Respect a user-requested phase boundary (for example, an evaluator-owned browser pass). + +## 6. Evidence and evaluation + +Hand over changed files, tests/build result, screenshot paths with viewport/state, visible findings and fixes, interaction results, remaining debt, and any image-delivery limitation. Never promise to outperform another model or tool. + +For comparisons, use the same brief, starting files, GPT model/reasoning, viewports, and declared time/token caps. Record actual elapsed time, input/output/reasoning/cache usage and subagent usage when available. A timeout is a task-completion result, not a visual-quality score. Blind A/B artifacts for a human reviewer; hide tool labels until ratings are recorded. Evaluate hierarchy, typography, spacing, consistency, responsiveness, accessibility, and primary-task completion. One sample and the generating model's own score cannot establish general superiority over Claude Code. diff --git a/.config/opencode/skills/frontend-workflow/references/dotfiles-workbench.md b/.config/opencode/skills/frontend-workflow/references/dotfiles-workbench.md new file mode 100644 index 0000000..728b33c --- /dev/null +++ b/.config/opencode/skills/frontend-workflow/references/dotfiles-workbench.md @@ -0,0 +1,13 @@ +# Dotfiles workbench: project-specific context + +Evidence: the repository README describes macOS developer tooling, productivity, aesthetics, and developer experience. Existing configuration is primarily terminal/editor tooling, not a committed web design system. + +Audience: developer checking a workstation configuration. Primary action for the evaluation fixture: run a safe simulated readiness check and understand any failure. All fixture data must be labelled demo data; never imply it is a real system inspection. + +Approved visual references: none supplied or found for this new web surface. Impeccable and Vercel are process references, not approval to imitate their sites. Any user-supplied project reference supersedes these proposals. + +Proposed defaults for the fixture only: calm, precise, task-focused light interface; system-ui body text, monospace for technical values; 16px body with 1.5 line-height and 14px minimum secondary text; canvas #f5f7fb, surface #fff, ink #172234, secondary #475569, action #1d4ed8. Verify rendered contrast rather than assuming tokens guarantee it. + +Proposed spacing: 4/8/12/16/24/32/48px, content near 1040px maximum, 16-24px mobile gutters. Components: labelled filter/scenario controls, one dominant Run checks action, readable check rows, explicit result/empty/error feedback. Use no decorative imagery unless it explains the task. Preserve focus, disabled/loading feedback and reduced motion. + +Do not apply these defaults to other applications. Read their design system and audience first. diff --git a/.config/opencode/skills/impeccable/LICENSE b/.config/opencode/skills/impeccable/LICENSE new file mode 100644 index 0000000..bb3f6d2 --- /dev/null +++ b/.config/opencode/skills/impeccable/LICENSE @@ -0,0 +1,191 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to the Licensor for inclusion in the Work by the copyright + owner or by an individual or Legal Entity authorized to submit on + behalf of the copyright owner. For the purposes of this definition, + "submitted" means any form of electronic, verbal, or written + communication sent to the Licensor or its representatives, including + but not limited to communication on electronic mailing lists, source + code control systems, and issue tracking systems that are managed by, + or on behalf of, the Licensor for the purpose of discussing and + improving the Work, but excluding communication that is conspicuously + marked or otherwise designated in writing by the copyright owner as + "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2025 Paul Bakaus + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/.config/opencode/skills/impeccable/SKILL.md b/.config/opencode/skills/impeccable/SKILL.md new file mode 100644 index 0000000..6599542 --- /dev/null +++ b/.config/opencode/skills/impeccable/SKILL.md @@ -0,0 +1,90 @@ +--- +name: impeccable +description: Use when the user wants to design, redesign, shape, critique, audit, polish, clarify, distill, harden, optimize, adapt, animate, colorize, extract, or otherwise improve a frontend interface. Covers websites, landing pages, dashboards, product UI, app shells, components, forms, settings, onboarding, and empty states. Handles UX review, visual hierarchy, information architecture, cognitive load, accessibility, performance, responsive behavior, theming, anti-patterns, typography, fonts, spacing, layout, alignment, color, motion, micro-interactions, UX copy, error states, edge cases, i18n, and reusable design systems or tokens. Also use for bland designs that need to become bolder or more delightful, loud designs that should become quieter, live browser iteration on UI elements, or ambitious visual effects that should feel technically extraordinary. Not for backend-only or non-UI tasks. +version: 4.3.1 +user-invocable: true +argument-hint: "[shape · audit|critique · animate|bolder|colorize|delight|layout|overdrive|quieter|typeset · adapt|clarify|distill · harden|onboard|optimize|polish · init|document|extract|live|generate] [target]" +license: Apache 2.0 +allowed-tools: + - Bash(npx impeccable *) + - Bash(.opencode/skills/impeccable/scripts/impeccable *) +--- + +This skill gives you the tools and permission to create design that earns to be called out-of-distribution craft: Whereas before, your design work would have been safe, timid and measured, you now approach every design task as an award-winning design director with impeccable understanding for what makes exceptional design work: production-grade code, peak creativity, a clear POV, deep understanding of the needs of the client and users, and exceptional craft. + +Core principles: +- Go all out. No hedging, no shortcuts. The deliverable must be complete (except assets the user must provide). +- Dream big and bold. Distinct, beautiful, outstanding and highly inspiring work. +- Verify in bounded passes, not a loop, and the ceiling covers the whole cycle: screenshots, defect scans, micro-edits, and rebuilds alike. Build fully, inspect once with a batched round (desktop and mobile together on the web; the shipped device classes on a native platform), fix everything it shows in one batch, confirm with at most one more round, and stop polishing. Open-ended self-QA burns the user's money doing worse what the finish handoffs do better. + +## Setup + +1. Run `/scripts/impeccable context` once per session, where `` is the directory that contains this SKILL.md (the skill folder, not a plugin root two levels above it); keep cwd at the user's project. That base directory resolves every `.opencode/skills/impeccable/scripts/impeccable ` command in this skill and its references, and `.opencode/skills/impeccable/scripts` is the fallback only when the runtime reports no base directory. On a Windows shell without `sh`, call `.opencode/skills/impeccable/scripts/impeccable.cmd` instead. The launcher runs a self-contained binary that ships next to it or is downloaded once on first run; no Node or other runtime is required. Pass a named source file or route as `--target `. It loads PRODUCT.md, DESIGN.md, the matching surface brief, and native-platform guidance when applicable; follow its directives and do not rerun it. +2. Load the request's playbook: its Commands-table reference for an explicit/implied sub-command, or [reference/new-work.md](reference/new-work.md) for a new surface or replacement visual world. Inspect target and incumbent visual truth before editing. When the app cannot run, start with committed visual-regression goldens or screenshot fixtures; verify target and freshness against current tokens, CSS, components, or assets, resolve conflicts, and compare theme/variant captures. +3. After resolving analysis and direction, read [reference/craft-floor.md](reference/craft-floor.md) immediately before any UI edit, including small refinements. It carries the quality floor, the absolute bans, and the reflexes no detector catches. Do not load it for planning-only work. + +**Launcher unavailable:** On refusal or failure, send a separate message **before the next tool call**: “Context loading did not run; I’ll read the existing project context directly.” Then read existing PRODUCT.md and DESIGN.md without inventing missing context, follow applicable steps 2–3, and continue through permitted tools. This applies to planning and editing; launcher failure alone does not block either. + +## How to design + +- **The brief wins.** Honor pinned aesthetics, eras, materials, fonts, and palettes even when they conflict with a saturated-pattern warning. Redirecting a clear brief toward your taste is failure. +- **Refinement preserves; redesign replaces.** Refinement keeps the incumbent identity, behavior, copy, and everything outside scope. Ask before replacing factual copy or adding claims. Redesign keeps product truth, content, function, native affordances, and constraints, but treats the old look as evidence and anti-reference; choose a replacement world in new-work and replace DESIGN.md. Never split the difference into polish on the discarded look. +- **Visual authority is evidence, not a filename.** Missing DESIGN.md alone does not make a project greenfield; new-work decides whether to preserve, expand, or replace the incumbent world. + +## Modes + +The mode names what the visitor's success looks like on this surface. + +- **Persuade:** the visitor decides and acts; design is the product. Landing pages, marketing, campaigns, pricing. Earn attention and action. Ship real imagery when the brief needs it; follow the committed world, not category habit. +- **Operate:** the visitor completes a task. App UI, dashboards, editors, admin, settings, tools. Scanability, consistency, native expectations, and the real usage scene outrank expression. Brand lives in precise details. +- **Read:** the visitor understands something. Docs, articles, guides, help, changelogs. Structure for comprehension, then make the reading experience worth staying in. +- **Experience:** the visitor is inside the work itself. Portfolios, galleries, showcases. Let the artifact lead from the first viewport; the interface recedes. + +Choose the mode from the requested surface, not the product, and persist it only in that surface brief. A tool's landing page is still Persuade; a fashion house's documentation is still Read; a docs index is Read, not Persuade. See [new-work.md](reference/new-work.md) for new surfaces and [operate.md](reference/operate.md) for deeper Operate/Read guidance. + +## Commands + +| Command | Category | Description | Reference | +|---|---|---|---| +| `craft [feature]` | Build | Deprecated alias for an ordinary new-work request | [reference/craft.md](reference/craft.md) | +| `shape [feature]` | Build | Plan UX/UI before writing code | [reference/shape.md](reference/shape.md) | +| `init` | Build | Capture durable product context in PRODUCT.md | [reference/init.md](reference/init.md) | +| `document` | Build | Generate DESIGN.md from existing project code | [reference/document.md](reference/document.md) | +| `extract [target]` | Build | Pull reusable tokens and components into design system | [reference/extract.md](reference/extract.md) | +| `critique [target]` | Evaluate | UX design review with heuristic scoring | [reference/critique.md](reference/critique.md) | +| `audit [target]` | Evaluate | Technical quality checks (a11y, perf, responsive) | [reference/audit.md](reference/audit.md) · native: [reference/audit.native.md](reference/audit.native.md) | +| `polish [target]` | Refine | Final quality pass before shipping | [reference/polish.md](reference/polish.md) | +| `bolder [target]` | Refine | Amplify safe or bland designs | [reference/bolder.md](reference/bolder.md) | +| `quieter [target]` | Refine | Tone down aggressive or overstimulating designs | [reference/quieter.md](reference/quieter.md) | +| `distill [target]` | Refine | Strip to essence, remove complexity | [reference/distill.md](reference/distill.md) | +| `harden [target]` | Refine | Production-ready: errors, i18n, edge cases | [reference/harden.md](reference/harden.md) | +| `onboard [target]` | Refine | Design first-run flows, empty states, activation | [reference/onboard.md](reference/onboard.md) | +| `animate [target]` | Enhance | Add purposeful animations and motion | [reference/animate.md](reference/animate.md) | +| `colorize [target]` | Enhance | Add strategic color to monochromatic UIs | [reference/colorize.md](reference/colorize.md) | +| `typeset [target]` | Enhance | Improve typography hierarchy and fonts | [reference/typeset.md](reference/typeset.md) | +| `layout [target]` | Enhance | Fix spacing, rhythm, and visual hierarchy | [reference/layout.md](reference/layout.md) | +| `delight [target]` | Enhance | Add personality and memorable touches | [reference/delight.md](reference/delight.md) | +| `overdrive [target]` | Enhance | Push past conventional limits | [reference/overdrive.md](reference/overdrive.md) | +| `clarify [target]` | Fix | Improve UX copy, labels, and error messages | [reference/clarify.md](reference/clarify.md) | +| `adapt [target]` | Fix | Adapt for different devices and screen sizes | [reference/adapt.md](reference/adapt.md) · native: [reference/adapt.native.md](reference/adapt.native.md) | +| `optimize [target]` | Fix | Diagnose and fix UI performance | [reference/optimize.md](reference/optimize.md) | +| `live` | Iterate | Visual variant mode: pick elements in the browser, iterate on alternatives | [reference/live.md](reference/live.md) | +| `generate [n] [action] [element]` | Iterate | Variants, versions, or alternatives of a named element to choose from in the live browser; no manual picking | [reference/generate.md](reference/generate.md) | + +Routing: + +- **No argument:** read [routing.md](reference/routing.md) and present its context-aware menu; never auto-run a command. +- **Explicit or clearly implied request to run a command:** load its reference (native variant on native platforms) and follow it. Ask once if two commands fit. +- **Workflow or command-selection question:** read [Workflow questions](reference/routing.md#workflow-questions). +- **Otherwise:** treat the request as general design work. Missing PRODUCT.md routes a new surface or replacement world through init, then new-work; a narrow refinement of existing code proceeds on the incumbent implementation as `impeccable context` directs, offering init afterward rather than blocking on it. +- `teach` aliases `init`. `craft` is a deprecated alias for ordinary new-work and adds nothing. `shape` owns task discovery, then enters new-work only for visual-world and surface-concept decisions. + +After init writes PRODUCT.md, resume without rerunning `impeccable context`; init loads the native platform reference itself when the platform it recorded is `ios`, `android`, or `adaptive`. + +**Pin / Unpin:** `.opencode/skills/impeccable/scripts/impeccable pin ` creates or removes a standalone `/` shortcut. Report the script's result concisely; relay stderr verbatim on error. + +**Hooks:** `/impeccable hooks ` manages the design detector hook for this project (auto-runs the detector after UI file edits and surfaces findings). Load [reference/hooks.md](reference/hooks.md) when the user invokes it with any argument. + +**Doctor:** `/impeccable doctor` reports and repairs drift between this project's Impeccable artifacts (PRODUCT.md, DESIGN.md and its sidecar, config, surface briefs, the hook) and what this version reads. Load [reference/doctor.md](reference/doctor.md) when the user invokes it, or when they ask what is out of date, stale, or needs refreshing. A `CONTEXT_STALE` directive in Setup's output is the cheap subset of the same report; act on it there per its own instructions rather than running doctor unasked. + +**Never repair drift as a side effect of a design task.** A `CONTEXT_STALE` finding is reported, not acted on, unless the user asks. The one exception is a finding marked `auto`, which the next write to that file performs anyway. \ No newline at end of file diff --git a/.config/opencode/skills/impeccable/reference/adapt.md b/.config/opencode/skills/impeccable/reference/adapt.md new file mode 100644 index 0000000..58d54aa --- /dev/null +++ b/.config/opencode/skills/impeccable/reference/adapt.md @@ -0,0 +1,318 @@ +> **Additional context needed**: target platforms/devices and usage contexts. + +Adapt an existing design to a different context: another screen size, device, platform, or use case. The trap is treating adaptation as scaling. The job is rethinking the experience for the new context. + +**Web only** (mobile web included). Native platforms (`ios` / `android` / `adaptive`) route to [adapt.native.md](adapt.native.md) instead; if the project is native, switch to it now. + +--- + +## Assess Adaptation Challenge + +Understand what needs adaptation and why: + +1. **Identify the source context**: + - What was it designed for originally? (Desktop web? Mobile app?) + - What assumptions were made? (Large screen? Mouse input? Fast connection?) + - What works well in current context? + +2. **Understand target context**: + - **Device**: Mobile, tablet, desktop, TV, watch, print? + - **Input method**: Touch, mouse, keyboard, voice, gamepad? + - **Screen constraints**: Size, resolution, orientation? + - **Connection**: Fast wifi, slow 3G, offline? + - **Usage context**: On-the-go vs desk, quick glance vs focused reading? + - **User expectations**: What do users expect on this platform? + +3. **Identify adaptation challenges**: + - What won't fit? (Content, navigation, features) + - What won't work? (Hover states on touch, tiny touch targets) + - What's inappropriate? (Desktop patterns on mobile, mobile patterns on desktop) + +**CRITICAL**: Adaptation is rethinking the experience for the new context, not scaling pixels. + +## Plan Adaptation Strategy + +Create context-appropriate strategy: + +### Mobile Adaptation (Desktop → Mobile) + +**Layout Strategy**: +- Single column instead of multi-column +- Vertical stacking instead of side-by-side +- Full-width components instead of fixed widths +- Bottom navigation instead of top/side navigation + +**Interaction Strategy**: +- Touch targets 44x44px minimum (not hover-dependent) +- Swipe gestures where appropriate (lists, carousels) +- Bottom sheets instead of dropdowns +- Thumbs-first design (controls within thumb reach) +- Larger tap areas with more spacing + +**Content Strategy**: +- Progressive disclosure (don't show everything at once) +- Prioritize primary content (secondary content in tabs/accordions) +- Shorter text (more concise) +- Larger text (16px minimum) + +**Navigation Strategy**: +- Hamburger menu or bottom navigation +- Reduce navigation complexity +- Sticky headers for context +- Back button in navigation flow + +### Tablet Adaptation (Hybrid Approach) + +**Layout Strategy**: +- Two-column layouts (not single or three-column) +- Side panels for secondary content +- Master-detail views (list + detail) +- Adaptive based on orientation (portrait vs landscape) + +**Interaction Strategy**: +- Support both touch and pointer +- Touch targets 44x44px but allow denser layouts than phone +- Side navigation drawers +- Multi-column forms where appropriate + +### Desktop Adaptation (Mobile → Desktop) + +**Layout Strategy**: +- Multi-column layouts (use horizontal space) +- Side navigation always visible +- Multiple information panels simultaneously +- Fixed widths with max-width constraints (don't stretch to 4K) + +**Interaction Strategy**: +- Hover states for additional information +- Keyboard shortcuts +- Right-click context menus +- Drag and drop where helpful +- Multi-select with Shift/Cmd + +**Content Strategy**: +- Show more information upfront (less progressive disclosure) +- Data tables with many columns +- Richer visualizations +- More detailed descriptions + +### Print Adaptation (Screen → Print) + +**Layout Strategy**: +- Page breaks at logical points +- Remove navigation, footer, interactive elements +- Black and white (or limited color) +- Proper margins for binding + +**Content Strategy**: +- Expand shortened content (show full URLs, hidden sections) +- Add page numbers, headers, footers +- Include metadata (print date, page title) +- Convert charts to print-friendly versions + +### Email Adaptation (Web → Email) + +**Layout Strategy**: +- Narrow width (600px max) +- Single column only +- Inline CSS (no external stylesheets) +- Table-based layouts (for email client compatibility) + +**Interaction Strategy**: +- Large, obvious CTAs (buttons not text links) +- No hover states (not reliable) +- Deep links to web app for complex interactions + +## Implement Adaptations + +Apply changes systematically: + +### Responsive Breakpoints + +Choose appropriate breakpoints: +- Mobile: 320px-767px +- Tablet: 768px-1023px +- Desktop: 1024px+ +- Or content-driven breakpoints (where design breaks) + +### Layout Adaptation Techniques + +- **CSS Grid/Flexbox**: Reflow layouts automatically +- **Container Queries**: Adapt based on container, not viewport +- **`clamp()`**: Fluid sizing between min and max +- **Media queries**: Different styles for different contexts +- **Display properties**: Show/hide elements per context + +### Touch Adaptation + +- Increase touch target sizes (44x44px minimum) +- Add more spacing between interactive elements +- Remove hover-dependent interactions +- Add touch feedback (ripples, highlights) +- Consider thumb zones (easier to reach bottom than top) + +### Content Adaptation + +- Use `display: none` sparingly (still downloads) +- Progressive enhancement (core content first, enhancements on larger screens) +- Lazy loading for off-screen content +- Responsive images (`srcset`, `picture` element) + +### Navigation Adaptation + +- Transform complex nav to hamburger/drawer on mobile +- Bottom nav bar for mobile apps +- Persistent side navigation on desktop +- Breadcrumbs on smaller screens for context + +**IMPORTANT**: Test on real devices. Device emulation in DevTools is helpful but not perfect. + +**NEVER**: +- Hide core functionality on mobile (if it matters, make it work) +- Assume desktop = powerful device (consider accessibility, older machines) +- Use different information architecture across contexts (confusing) +- Break user expectations for platform (mobile users expect mobile patterns) +- Forget landscape orientation on mobile/tablet +- Use generic breakpoints blindly (use content-driven breakpoints) +- Ignore touch on desktop (many desktop devices have touch) + +## Verify Adaptations + +Test thoroughly across contexts: + +- **Real devices**: Test on actual phones, tablets, desktops +- **Different orientations**: Portrait and landscape +- **Different browsers**: Safari, Chrome, Firefox, Edge +- **Different OS**: iOS, Android, Windows, macOS +- **Different input methods**: Touch, mouse, keyboard +- **Edge cases**: Very small screens (320px), very large screens (4K) +- **Slow connections**: Test on throttled network + +**Custom controls** (sliders, drag surfaces, scrollable control strips): a before/after slider can pass every width check above and still refuse to drag on iOS, so exercise each one in scope in the same batched round as the checks above: + +- **Primary gesture**: Tap it and confirm it responds as designed, then drag it with the target input method; the drag must complete, not just start +- **Scroll across it**: A swipe along the page's scroll axis across the control scrolls the page or container without activating it; a drag that starts on the control along its axis moves the control, not the page. Neither failure throws an error, so try both +- **Evidence**: Say what produced the evidence: an emulated viewport, synthesized touch input through a browser tool, which engine ran it (Chromium is not Safari), or a physical device. Screenshots and resized viewports verify layout, never a gesture. Name what stayed untested and move on; unreachable hardware is a reported gap, not a blocker + +When the adaptation feels native to each context, hand off to `/impeccable polish` for the final pass. + +--- + +## Reference Material + +The sections below were previously `responsive-design.md` and live inline now so the adapt flow has its deep responsive reference in one place. + +### Responsive Design + +#### Mobile-First: Write It Right + +Start with base styles for mobile, use `min-width` queries to layer complexity. Desktop-first (`max-width`) means mobile loads unnecessary styles first. + +#### Breakpoints: Content-Driven + +Don't chase device sizes; let content tell you where to break. Start narrow, stretch until design breaks, add breakpoint there. Three breakpoints usually suffice (640, 768, 1024px). Use `clamp()` for fluid values without breakpoints. + +#### Detect Input Method, Not Just Screen Size + +**Screen size doesn't tell you input method.** A laptop with touchscreen, a tablet with keyboard. Use pointer and hover queries: + +```css +/* Fine pointer (mouse, trackpad) */ +@media (pointer: fine) { + .button { padding: 8px 16px; } +} + +/* Coarse pointer (touch, stylus) */ +@media (pointer: coarse) { + .button { padding: 12px 20px; } /* Larger touch target */ +} + +/* Device supports hover */ +@media (hover: hover) { + .card:hover { transform: translateY(-2px); } +} + +/* Device doesn't support hover (touch) */ +@media (hover: none) { + .card { /* No hover state - use active instead */ } +} +``` + +**Critical**: Don't rely on hover for functionality. Touch users can't hover. + +#### Safe Areas: Handle the Notch + +Modern phones have notches, rounded corners, and home indicators. Use `env()`: + +```css +body { + padding-top: env(safe-area-inset-top); + padding-bottom: env(safe-area-inset-bottom); + padding-left: env(safe-area-inset-left); + padding-right: env(safe-area-inset-right); +} + +/* With fallback */ +.footer { + padding-bottom: max(1rem, env(safe-area-inset-bottom)); +} +``` + +**Enable viewport-fit** in your meta tag: +```html + +``` + +#### Responsive Images: Get It Right + +##### srcset with Width Descriptors + +```html +Hero image +``` + +**How it works**: +- `srcset` lists available images with their actual widths (`w` descriptors) +- `sizes` tells the browser how wide the image will display +- Browser picks the best file based on viewport width AND device pixel ratio + +##### Picture Element for Art Direction + +When you need different crops/compositions (not just resolutions): + +```html + + + + ... + +``` + +#### Layout Adaptation Patterns + +**Navigation**: Three stages: hamburger + drawer on mobile, horizontal compact on tablet, full with labels on desktop. **Tables**: Transform to cards on mobile using `display: block` and `data-label` attributes. **Progressive disclosure**: Use `
/` for content that can collapse on mobile. + +#### Testing: Don't Trust DevTools Alone + +DevTools device emulation is useful for layout but misses: + +- Actual touch interactions +- Real CPU/memory constraints +- Network latency patterns +- Font rendering differences +- Browser chrome/keyboard appearances + +**Test on at least**: One real iPhone, one real Android, a tablet if relevant. Cheap Android phones reveal performance issues you'll never see on simulators. + +--- + +**Avoid**: Desktop-first design. Device detection instead of feature detection. Separate mobile/desktop codebases. Ignoring tablet and landscape. Assuming all mobile devices are powerful. diff --git a/.config/opencode/skills/impeccable/reference/adapt.native.md b/.config/opencode/skills/impeccable/reference/adapt.native.md new file mode 100644 index 0000000..f1ccd65 --- /dev/null +++ b/.config/opencode/skills/impeccable/reference/adapt.native.md @@ -0,0 +1,58 @@ +> **Additional context needed**: target platforms/devices and usage contexts. + +Adapt an existing **native** design (`ios` / `android` / `adaptive`) to a different context: another device class, orientation, platform, or origin. The trap is treating adaptation as scaling. The job is rethinking the experience for the new context, inside the platform conventions of [ios.md](ios.md) / [android.md](android.md); read the target platform's reference before planning if Setup hasn't already. + +## Assess Adaptation Challenge + +1. **Source context**: what was it designed for, and what assumptions did it make? (Phone-only? Portrait-only? One platform's idioms? A website?) +2. **Target context**: which device class (phone, tablet, foldable), orientation, platform, and usage posture (one-handed on the go vs two-handed at rest)? +3. **What breaks**: navigation that doesn't fit the target, layouts that stretch instead of restructure, gestures or controls that don't exist there? + +## Adaptation Strategies + +### Phone → Tablet (iPad / large screens) + +- **Restructure, don't stretch.** A scaled-up phone UI on a tablet is the failure mode. Use size classes (iOS) / window size classes (Android) to switch structure. +- **Navigation changes shape**: tab bar stays or becomes a sidebar on iPad; Android navigation bar becomes a rail or drawer on expanded width. +- **Use the width**: split view / master-detail (list + detail side by side), multi-column grids, popovers where phones used sheets. +- **Multitasking is a size, not an edge case**: iPad Split View and Android multi-window can hand you a phone-width window on a tablet; size-class-driven layout handles both for free. + +### Orientation & foldables + +- Landscape restructures (side-by-side panes, repositioned controls); never clip or letterbox. Lock orientation only when the task truly demands it. +- Foldables (Android): react to posture and hinge via window size classes; test folded, unfolded, and tabletop. + +### Platform → platform (iOS ↔ Android) + +Translate idioms; never transplant them: + +| iOS | Android | +|---|---| +| Tab bar | Navigation bar / rail / drawer | +| Edge-swipe back, back chevron | Predictive Back gesture / button | +| Switch, segmented control, system pickers | Material switch, chips, Material pickers | +| Action sheet | Bottom sheet / Material dialog | +| SF Symbols, SF Pro, Dynamic Type | Material Symbols, Roboto, sp scaling | +| Semantic system colors, materials | Material color roles, tonal elevation | +| System push/sheet transitions | Container transform, shared-axis, fade-through | + +Rebuild navigation and controls in the target's vocabulary; carry over the brand's expressive layer (palette intent, type accent, motion personality) through the target's theming system. + +### Web → native (porting a website or web app) + +Reconform, don't reflow. Replace web navigation with the platform's model, HTML-shaped controls with platform controls, hover affordances with touch-first ones, and px-based type with Dynamic Type / sp. Then treat the result to the full platform reference; the slop test there is the acceptance bar. + +## Implement & Verify + +- Drive structure from **size classes / window size classes**, never from device-model checks. +- Respect safe areas and window insets in every new configuration (notch, hinge, status bar, keyboard). +- Test on simulators for breadth, then real hardware for truth: at least one phone and one tablet per shipped platform, both orientations, split-screen where supported. + +When the adaptation feels native to each context, hand off to `/impeccable polish` for the final pass. + +**NEVER**: +- Ship a stretched phone layout on a tablet +- Port one platform's controls or navigation onto the other +- Hide core functionality on smaller devices (if it matters, make it work) +- Lock orientation to dodge a layout bug +- Trust simulators alone (posture, gestures, and performance need hardware) diff --git a/.config/opencode/skills/impeccable/reference/android.md b/.config/opencode/skills/impeccable/reference/android.md new file mode 100644 index 0000000..1f67a6b --- /dev/null +++ b/.config/opencode/skills/impeccable/reference/android.md @@ -0,0 +1,46 @@ +# Android platform + +For native Android apps: Jetpack Compose, Android Views, React Native, Expo, Flutter shipping to Android hardware. + +On native, the visitor mode narrows what expression may override. Material Design 3 governs structure, navigation, and interaction in every mode; brand expresses through Material's theming (color roles, type scale, shape, motion). A Material-everywhere cross-platform app that also ships to iPhone still owes iOS its OS guarantees on that hardware: safe-area insets, Reduce Motion, edge-swipe back. + +## The Android slop test + +Would a fluent Android user trust this app, or trip on off-spec components? The most common tell is an iOS app wearing Android's skin: a bottom-only navigation copied from iPhone, a back arrow that ignores the system Back gesture, Cupertino-shaped switches and dialogs. Material 3 is the rulebook; follow its components and theme the brand through it. + +## Layout & structure + +- **Material navigation, matched to size.** Navigation bar (bottom, 3–5 destinations) on compact width; navigation rail or drawer on expanded width. Never ship a phone bottom-bar untouched on a tablet. +- **System Back always works.** Honor the predictive Back gesture and Back button; never trap the user or hijack the gesture. +- **Edge-to-edge with window insets.** Apply the status bar, navigation bar, display cutout, and IME insets so content never hides behind system bars or the keyboard. +- **Top app bar for screen context**; pair with a FAB when the screen has a single primary action. + +## Touch targets + +- **48×48 dp minimum** for every touch target, with at least 8 dp between them. + +## Typography + +- **Material type scale.** Display, Headline, Title, Body, Label roles (large/medium/small each). Map text to roles; never hand-pick sizes per screen. +- **Roboto is the system face**; theme a brand face in through the type scale, keeping body, labels, and controls legible and consistent. +- **sp units, never fixed px**, so type follows the system font-size setting. + +## Color & theming + +- **Material color roles** (primary, on-primary, surface, surface-variant, secondary-container, outline, error). Role tokens resolve light/dark and contrast variants automatically; raw hex breaks there. +- **Dynamic Color (Material You)** where it fits: derive the scheme from the user's wallpaper on Android 12+, with a static fallback. +- **Dark theme is a first-class scheme.** Design and test it; never a quick invert. +- **Tonal elevation.** Convey elevation through the standard surface tonal levels (plus shadow where appropriate); no arbitrary drop shadows. + +## Components & motion + +- **Material components.** Buttons (filled / tonal / outlined / text), FAB, switches, chips, snackbars, bottom sheets, Material dialogs, navigation bar/rail/drawer. Never port iOS controls or invent equivalents. +- **One FAB, one primary action.** Never stack FABs or spend one on a secondary task. +- **Snackbars for transient feedback** (actionable when useful, never a toast for that); dialogs only for decisions that must interrupt. +- **Material motion patterns.** Container transform, shared-axis, fade-through, with standard easing and durations; honor the system Remove animations setting with a crossfade or instant cut. + +## Verifying the build + +- **Screenshots come from the emulator or a connected device, never a browser.** Build and install, then capture with `adb exec-out screencap -p > ` (pick a device with `adb -s ` when several are attached). Capture every device class the app ships to, at least one phone and, when tablets are a target, one tablet, and write the files where the review flow expects them. +- **Dark theme and font scale belong in the pass.** `adb shell cmd uimode night yes` flips the theme; `adb shell settings put system font_scale 1.3` (restore `1.0` after) catches the clipped labels a fixed layout hides; with several targets attached, the capture's `-s ` goes on these commands too. +- **Emulators give breadth; gestures, refresh rates, and performance need hardware.** Say which one produced the evidence. diff --git a/.config/opencode/skills/impeccable/reference/animate.md b/.config/opencode/skills/impeccable/reference/animate.md new file mode 100644 index 0000000..4ae4cc5 --- /dev/null +++ b/.config/opencode/skills/impeccable/reference/animate.md @@ -0,0 +1,89 @@ +> **Additional context needed**: performance constraints. + +Use motion to explain state, relationship, and hierarchy, or to create one authored moment the surface has earned. Decoration without purpose is animation debt. + +--- + +## Visitor mode + +- **Persuade + Experience:** motion may carry the voice. Prefer one rehearsed focal sequence to repeated section reveals. +- **Operate + Read:** motion serves feedback, state, and continuity. Keep routine transitions fast and do not make users wait through page-load choreography. +- **Native (`ios` / `android` / `adaptive`):** follow the Motion section of [ios.md](ios.md) or [android.md](android.md), including the platform's Reduce Motion behavior. Do not apply the web tooling below. + +## Find the job + +Inspect the existing motion language, interaction states, target devices, and performance budget. Find only the places where motion would: + +- acknowledge an action; +- make a state change or spatial relationship legible; +- preserve continuity through navigation or layout change; +- direct attention at a meaningful moment; +- embody the selected visual world. + +Ask only when a material constraint cannot be inferred. Do not animate a static area merely because it exists. + +## Set the motion thesis + +Write a short plan before implementation: + +- **Focal moment:** the one sequence or interaction that deserves authorship, if any. +- **Continuity:** the state, layout, or navigation changes that need explanation. +- **Feedback:** the controls and outcomes that need acknowledgment. +- **Budget:** which effects may be expensive and how often they run. + +The focal moment must come from this product and surface concept. A generic fade-and-rise, hover lift, parallax layer, or scroll reveal is not a thesis. + +## Choose material by meaning + +Transform and opacity are reliable foundations, not the entire palette. Choose properties for what the transition communicates: + +- **Continuity and relationship:** shared-element motion, FLIP-style transforms, view transitions, or deliberate spatial movement. +- **Focus and depth:** bounded blur, filter, backdrop, light, or shadow changes. +- **Reveal and composition:** masks, clip paths, cropping, or controlled occlusion. +- **Material and energy:** color, gradient position, texture, distortion, or shader effects when the world and runtime support them. +- **State and feedback:** the smallest change that makes cause and result unmistakable. + +Do not stack techniques for spectacle. One strong material idea, carried through the focal sequence and quiet supporting states, is usually enough. + +Sibling stagger is appropriate when a list appears as a list. Cap the total delay, and never reinterpret every scrolled section as a staggered list. + +## Timing and easing + +Timing should express distance and consequence: + +| Duration | Typical use | +|---|---| +| 100–150 ms | immediate feedback | +| 150–300 ms | routine state change | +| 300–500 ms | layout, overlay, or view transition | +| 500–800 ms | a deliberately authored focal entrance | + +Exit faster than entrance. Use natural deceleration such as `cubic-bezier(0.16, 1, 0.3, 1)` for confident arrivals; do not use bounce or elastic curves by reflex. Long feedback feels like latency. + +## Implement to the runtime + +- Use CSS transitions and keyframes for declarative state and bounded sequences. +- Use Web Animations API or the project's existing motion library for interruption, sequencing, and dynamic values. +- Use View Transitions or shared-element techniques when continuity across states is the point. +- Use scroll-driven motion only when the scroll relationship itself carries meaning, with a robust fallback. +- Do not add a dependency for an effect the existing stack can express cleanly. + +Keep content visible in the default state so failed scripts do not hide the page. Avoid casually animating layout-driving properties such as `width`, `height`, `top`, `left`, and margins; use FLIP, transforms, or grid techniques when appropriate. Bound blur, filter, shadow, canvas, and shader work to isolated regions. Apply `will-change` only during known animation. Measure on target viewports and devices rather than assuming transform means fast. + +## Accessibility and control + +Respect autoplay and sound preferences. Any nonessential loop must stop when offscreen or hidden. + +Every web animation needs a `prefers-reduced-motion` path with an intentional alternative. Remove or reduce spatial movement while preserving opacity, color, and state transitions that carry meaning. Reduced motion means fewer and gentler animations, not disabling all motion; feedback that confirms an action should remain legible. + +## Verify + +- The focal motion is specific to the selected world and surface. +- Every supporting animation explains feedback, state, or relationship. +- Interruption and repeated use behave correctly. +- Desktop, mobile, and keyboard paths remain usable. +- The `prefers-reduced-motion` path reduces movement without erasing meaningful feedback or state changes. +- Expensive effects stay smooth on the target device. +- Removing an animation would lose meaning or authored character, not merely decoration. + +When motion earns its place, hand off to `/impeccable polish` for the final pass. diff --git a/.config/opencode/skills/impeccable/reference/audit.md b/.config/opencode/skills/impeccable/reference/audit.md new file mode 100644 index 0000000..98c56d0 --- /dev/null +++ b/.config/opencode/skills/impeccable/reference/audit.md @@ -0,0 +1,137 @@ +Run systematic **technical** quality checks and generate a comprehensive report. Don't fix issues; document them for other commands to address. + +This is a code-level audit, not a design critique. Check what's measurable and verifiable in the implementation. + +**Web only.** Native platforms (`ios` / `android` / `adaptive`) route to [audit.native.md](audit.native.md) instead; if the project is native, switch to it now. + +## Diagnostic Scan + +Run comprehensive checks across 5 dimensions. Score each dimension 0-4 using the criteria below. + +### 1. Accessibility (A11y) + +**Check for**: +- **Contrast issues**: Text contrast ratios < 4.5:1 (or 7:1 for AAA) +- **Motion sensitivity**: `prefers-reduced-motion` needs an intentional alternative that preserves state change and hierarchy; flag a global `0.01ms` kill that destroys useful feedback, flashing above threshold, and motion that blocks focus, reading, or task completion +- **Missing ARIA**: Interactive elements without proper roles, labels, or states +- **Keyboard navigation**: Missing focus indicators, illogical tab order, keyboard traps +- **Semantic HTML**: Improper heading hierarchy, missing landmarks, divs instead of buttons +- **Alt text**: Missing or poor image descriptions +- **Form issues**: Inputs without labels, poor error messaging, missing required indicators + +**Score 0-4**: 0=Inaccessible (fails WCAG A), 1=Major gaps (few ARIA labels, no keyboard nav), 2=Partial (some a11y effort, significant gaps), 3=Good (WCAG AA mostly met, minor gaps), 4=Excellent (WCAG AA fully met, approaches AAA) + +### 2. Performance + +**Check for**: +- **Layout thrashing**: Reading/writing layout properties in loops +- **Expensive animations**: Casual layout-property animation, unbounded blur/filter/shadow effects, or effects that visibly drop frames +- **Missing optimization**: Images without lazy loading, unoptimized assets +- **will-change overuse**: `will-change` applied broadly or left on at rest (it is a targeted hint for known expensive animations, not a baseline requirement) +- **Bundle size**: Unnecessary imports, unused dependencies +- **Render performance**: Unnecessary re-renders, missing memoization + +**Score 0-4**: 0=Severe issues (layout thrash, unoptimized everything), 1=Major problems (no lazy loading, expensive animations), 2=Partial (some optimization, gaps remain), 3=Good (mostly optimized, minor improvements possible), 4=Excellent (fast, lean, well-optimized) + +### 3. Theming + +**Check for**: +- **Hard-coded colors**: Colors not using design tokens +- **Broken dark mode**: Missing dark mode variants, poor contrast in dark theme +- **Inconsistent tokens**: Using wrong tokens, mixing token types +- **Theme switching issues**: Values that don't update on theme change + +**Score 0-4**: 0=No theming (hard-coded everything), 1=Minimal tokens (mostly hard-coded), 2=Partial (tokens exist but inconsistently used), 3=Good (tokens used, minor hard-coded values), 4=Excellent (full token system, dark mode works perfectly) + +### 4. Responsive Design + +**Check for**: +- **Fixed widths**: Hard-coded widths that break on mobile +- **Touch targets**: Interactive elements < 44x44px +- **Broken touch interaction**: Custom sliders, drag surfaces, and scrollable control strips whose primary gesture fails under touch, that swallow page scroll or lose the drag to it, or that stay stuck after an interrupted gesture. Code tells: mouse-only handlers, no `touch-action` on a pointer-event drag surface, drag state that nothing clears on cancel, lost capture, or blur. Exercise the gesture when a browser tool can synthesize touch (a rendered viewport proves layout, not the gesture), then say what produced the evidence (emulated viewport, synthesized touch, which engine, physical device) and what stayed untested +- **Horizontal scroll**: Content overflow on narrow viewports +- **Text scaling**: Layouts that break when text size increases +- **Missing breakpoints**: No mobile/tablet variants + +**Score 0-4**: 0=Desktop-only (breaks on mobile), 1=Major issues (some breakpoints, many failures), 2=Partial (works on mobile, rough edges), 3=Good (responsive, minor touch target or overflow issues), 4=Excellent (fluid, all viewports, proper touch targets, gestures work under touch) + +### 5. Implementation Integrity (CRITICAL) + +Run the bundled detector and verify each finding in context. Look for repeated implementation shortcuts, design-system drift, misleading or decorative content, and structure that is interchangeable with an unrelated product. Keep deterministic findings separate from visual judgment and call out false positives. + +**Score 0-4**: 0=systemic drift, 1=major repeated failures, 2=several verified issues, 3=minor isolated issues, 4=coherent and intentional + +## Generate Report + +### Audit Health Score + +| # | Dimension | Score | Key Finding | +|---|-----------|-------|-------------| +| 1 | Accessibility | ? | [most critical a11y issue or "--"] | +| 2 | Performance | ? | | +| 3 | Responsive Design | ? | | +| 4 | Theming | ? | | +| 5 | Implementation Integrity | ? | | +| **Total** | | **??/20** | **[Rating band]** | + +**Rating bands**: 18-20 Excellent (minor polish), 14-17 Good (address weak dimensions), 10-13 Acceptable (significant work needed), 6-9 Poor (major overhaul), 0-5 Critical (fundamental issues) + +### Implementation Integrity Verdict +**Start here.** Pass/fail: does the implementation express a coherent product-specific system? Cite verified evidence and detector findings. + +### Executive Summary +- Audit Health Score: **??/20** ([rating band]) +- Total issues found (count by severity: P0/P1/P2/P3) +- Top 3-5 critical issues +- Recommended next steps + +### Detailed Findings by Severity + +Tag every issue with **P0-P3 severity**: +- **P0 Blocking**: Prevents task completion. Fix immediately +- **P1 Major**: Significant difficulty or WCAG AA violation. Fix before release +- **P2 Minor**: Annoyance, workaround exists. Fix in next pass +- **P3 Polish**: Nice-to-fix, no real user impact. Fix if time permits + +For each issue, document: +- **[P?] Issue name** +- **Location**: Component, file, line +- **Category**: Accessibility / Performance / Theming / Responsive / Implementation Integrity +- **Impact**: How it affects users +- **WCAG/Standard**: Which standard it violates (if applicable) +- **Recommendation**: How to fix it +- **Suggested command**: Which command to use (prefer: /impeccable adapt, /impeccable animate, /impeccable audit, /impeccable bolder, /impeccable clarify, /impeccable colorize, /impeccable critique, /impeccable delight, /impeccable distill, /impeccable document, /impeccable harden, /impeccable layout, /impeccable onboard, /impeccable optimize, /impeccable overdrive, /impeccable polish, /impeccable quieter, /impeccable shape, /impeccable typeset) + +### Patterns & Systemic Issues + +Identify recurring problems that indicate systemic gaps rather than one-off mistakes: +- "Hard-coded colors appear in 15+ components, should use design tokens" +- "Touch targets consistently too small (<44px) throughout mobile experience" + +### Positive Findings + +Note what's working well: good practices to maintain and replicate. + +## Recommended Actions + +List recommended commands in priority order (P0 first, then P1, then P2): + +1. **[P?] `/command-name`**: Brief description (specific context from audit findings) +2. **[P?] `/command-name`**: Brief description (specific context) + +**Rules**: Only recommend commands from: /impeccable adapt, /impeccable animate, /impeccable audit, /impeccable bolder, /impeccable clarify, /impeccable colorize, /impeccable critique, /impeccable delight, /impeccable distill, /impeccable document, /impeccable harden, /impeccable layout, /impeccable onboard, /impeccable optimize, /impeccable overdrive, /impeccable polish, /impeccable quieter, /impeccable shape, /impeccable typeset. Map findings to the most appropriate command. End with `/impeccable polish` as the final step if any fixes were recommended. + +After presenting the summary, tell the user: + +> You can ask me to run these one at a time, all at once, or in any order you prefer. +> +> Re-run `/impeccable audit` after fixes to see your score improve. + +**IMPORTANT**: Be thorough but actionable. Too many P3 issues creates noise. Focus on what actually matters. + +**NEVER**: +- Report issues without explaining impact (why does this matter?) +- Provide generic recommendations (be specific and actionable) +- Skip positive findings (celebrate what works) +- Forget to prioritize (everything can't be P0) +- Report false positives without verification diff --git a/.config/opencode/skills/impeccable/reference/audit.native.md b/.config/opencode/skills/impeccable/reference/audit.native.md new file mode 100644 index 0000000..fbccb56 --- /dev/null +++ b/.config/opencode/skills/impeccable/reference/audit.native.md @@ -0,0 +1,139 @@ +Run systematic **technical** quality checks on a native app (`ios` / `android` / `adaptive`) and generate a comprehensive report. Don't fix issues; document them for other commands to address. + +This is a code-level audit, not a design critique. Audit from source (SwiftUI / UIKit / Compose / React Native / Flutter); no browser tooling or `impeccable detect` applies. Score against the platform reference(s): [ios.md](ios.md) / [android.md](android.md), both for `adaptive`. Read them before scoring if Setup hasn't already. The report skeleton mirrors [audit.md](audit.md); keep the two in sync when changing it. + +## Diagnostic Scan + +Run comprehensive checks across 5 dimensions. Score each dimension 0-4 using the criteria below. + +### 1. Accessibility (VoiceOver / TalkBack) + +**Check for**: +- **Missing labels**: interactive elements without accessibility labels, traits/roles, or state announcements +- **Reading and focus order**: illogical traversal, unreachable controls, focus lost on navigation +- **Text scaling**: fixed point sizes defeating Dynamic Type (iOS) or px instead of sp (Android); layouts that clip or overlap at large sizes +- **Touch targets**: below 44 pt (iOS) / 48 dp (Android), or crammed without spacing +- **Reduce Motion ignored**: parallax and large slides with no crossfade alternative +- **Contrast**: text failing contrast in either appearance, light or dark + +**Score 0-4**: 0=Screen reader unusable, 1=Major gaps (unlabeled controls, no scaling), 2=Partial (labels exist, order or scaling breaks), 3=Good (minor gaps), 4=Excellent (labeled, ordered, scales cleanly, Reduce Motion honored) + +### 2. Performance + +**Check for**: +- **Slow startup**: heavy work on launch before first frame +- **Unvirtualized lists**: long content without FlatList / LazyColumn / List recycling +- **Main-thread jank**: synchronous work in scroll or gesture paths, dropped frames on 60/120 Hz +- **Wasted rendering**: unnecessary re-renders (React Native) or recompositions (Compose); missing memoization/keys +- **Image handling**: full-size images decoded for thumbnails, no caching +- **App weight**: bloated JS bundle or binary, unused dependencies + +**Score 0-4**: 0=Janky everywhere, 1=Major problems (unvirtualized lists, slow launch), 2=Partial, 3=Good (minor improvements possible), 4=Excellent (fast launch, smooth scroll, lean) + +### 3. Appearance & Theming + +**Check for**: +- **Hard-coded colors**: raw hex instead of semantic system colors (iOS) / Material color roles (Android) / design tokens +- **Broken dark appearance**: missing dark variants, poor contrast in dark, quick inverts +- **Dynamic Color** (Android 12+): no static fallback scheme, or ignored where it fits +- **Off-platform materials**: hand-rolled visual materials where system materials or tonal elevation are expected + +**Score 0-4**: 0=Hard-coded everything, 1=Minimal tokens, 2=Partial (tokens exist, inconsistently used), 3=Good (minor hard-coded values), 4=Excellent (semantic throughout, both appearances first-class) + +### 4. Platform Conformance (CRITICAL) + +Score against the loaded platform reference(s), including their slop tests. **Check for**: +- **Broken system gestures**: edge-swipe back disabled (iOS), predictive Back hijacked (Android) +- **Inset violations**: content under the notch, Dynamic Island, home indicator, status bar, or keyboard +- **Off-platform navigation**: custom global nav, overloaded tab bars, iOS patterns on Android or vice versa +- **Web-shaped controls**: HTML-style buttons, custom toggles, hover-dependent affordances +- **Icon drift**: mixed icon sets instead of SF Symbols / Material Symbols +- **System drift**: repeated shortcuts or decorative patterns that conflict with the product, platform, or established design system + +**Score 0-4**: 0=Web port (nothing native), 1=Heavy violations (3-4 kinds), 2=Some (1-2 noticeable), 3=Mostly conformant (subtle issues), 4=Fully native (a fluent user trusts every screen) + +### 5. Adaptivity + +**Check for**: +- **Stretched phone layouts**: tablet/iPad rendering a scaled-up phone UI instead of using size classes / window size classes +- **Orientation breakage**: landscape clipping, ignored, or locked without reason +- **Keyboard/IME handling**: inputs hidden behind the keyboard, no inset adjustment +- **Multitasking**: iPad Split View / Android multi-window breaking layout +- **Foldables**: hinge-unaware layouts on posture change (Android) + +**Score 0-4**: 0=One screen size only, 1=Major breakage (landscape or tablet broken), 2=Partial, 3=Good (minor edge cases), 4=Excellent (adapts across sizes, orientations, and windowing) + +## Generate Report + +### Audit Health Score + +| # | Dimension | Score | Key Finding | +|---|-----------|-------|-------------| +| 1 | Accessibility | ? | [most critical issue or "--"] | +| 2 | Performance | ? | | +| 3 | Appearance & Theming | ? | | +| 4 | Platform Conformance | ? | | +| 5 | Adaptivity | ? | | +| **Total** | | **??/20** | **[Rating band]** | + +**Rating bands**: 18-20 Excellent (minor polish), 14-17 Good (address weak dimensions), 10-13 Acceptable (significant work needed), 6-9 Poor (major overhaul), 0-5 Critical (fundamental issues) + +### Platform Conformance Verdict +**Start here.** Pass/fail: does this read as a native app or a ported website? List specific violations. Be brutally honest. + +### Executive Summary +- Audit Health Score: **??/20** ([rating band]) +- Total issues found (count by severity: P0/P1/P2/P3) +- Top 3-5 critical issues +- Recommended next steps + +### Detailed Findings by Severity + +Tag every issue with **P0-P3 severity**: +- **P0 Blocking**: Prevents task completion. Fix immediately +- **P1 Major**: Significant difficulty or platform-guideline violation. Fix before release +- **P2 Minor**: Annoyance, workaround exists. Fix in next pass +- **P3 Polish**: Nice-to-fix, no real user impact. Fix if time permits + +For each issue, document: +- **[P?] Issue name** +- **Location**: Screen, file, line +- **Category**: Accessibility / Performance / Theming / Conformance / Adaptivity +- **Impact**: How it affects users +- **Guideline**: The HIG / Material rule it violates (if applicable) +- **Recommendation**: How to fix it +- **Suggested command**: Which command to use (prefer: /impeccable adapt, /impeccable animate, /impeccable audit, /impeccable bolder, /impeccable clarify, /impeccable colorize, /impeccable critique, /impeccable delight, /impeccable distill, /impeccable document, /impeccable harden, /impeccable layout, /impeccable onboard, /impeccable optimize, /impeccable overdrive, /impeccable polish, /impeccable quieter, /impeccable shape, /impeccable typeset) + +### Patterns & Systemic Issues + +Identify recurring problems that indicate systemic gaps rather than one-off mistakes: +- "Hard-coded colors appear in 15+ screens, should use semantic colors" +- "Touch targets consistently below 44 pt throughout the tab bar and list rows" + +### Positive Findings + +Note what's working well: good practices to maintain and replicate. + +## Recommended Actions + +List recommended commands in priority order (P0 first, then P1, then P2): + +1. **[P?] `/command-name`**: Brief description (specific context from audit findings) +2. **[P?] `/command-name`**: Brief description (specific context) + +**Rules**: Only recommend commands from: /impeccable adapt, /impeccable animate, /impeccable audit, /impeccable bolder, /impeccable clarify, /impeccable colorize, /impeccable critique, /impeccable delight, /impeccable distill, /impeccable document, /impeccable harden, /impeccable layout, /impeccable onboard, /impeccable optimize, /impeccable overdrive, /impeccable polish, /impeccable quieter, /impeccable shape, /impeccable typeset. Map findings to the most appropriate command. End with `/impeccable polish` as the final step if any fixes were recommended. + +After presenting the summary, tell the user: + +> You can ask me to run these one at a time, all at once, or in any order you prefer. +> +> Re-run `/impeccable audit` after fixes to see your score improve. + +**IMPORTANT**: Be thorough but actionable. Too many P3 issues creates noise. Focus on what actually matters. + +**NEVER**: +- Report issues without explaining impact (why does this matter?) +- Provide generic recommendations (be specific and actionable) +- Skip positive findings (celebrate what works) +- Forget to prioritize (everything can't be P0) +- Report false positives without verification diff --git a/.config/opencode/skills/impeccable/reference/bolder.md b/.config/opencode/skills/impeccable/reference/bolder.md new file mode 100644 index 0000000..25fdcdb --- /dev/null +++ b/.config/opencode/skills/impeccable/reference/bolder.md @@ -0,0 +1,33 @@ +> **Additional context needed**: which section is the target, and what must stay untouched. + +An open direction round owns the word first: "bolder" said while a direction decision is on the table is the Bolder hand register steer, a fresh deal of foreign forms (see new-work.md), not this command. This command refines a surface whose world already shipped. + +"Bolder" is an amplification request, and almost always it is scoped to something that already exists. The surrounding page, its system, and its conventions are the given. Your job is to raise one part to the conviction the rest already implies, without rebuilding anything the brief did not name. The reflex answer, reaching for more effects, is the opposite of bold; reject it first. + +## Scope is sovereign + +"Everything else stays" is a literal instruction. Touch only the named target. Do not restyle its neighbors, do not migrate the page to a new idea, do not add colors, fonts, radii, shadows, or system primitives the surface does not already own. If the existing system genuinely cannot express the direction, do not expand it on your own. STOP and call the `question` tool to clarify. Name the exact addition and the job it would do. + +## Why it reads flat + +A section usually reads flat for reasons its neighbors have already solved. Look at what the rest of the page does that this section does not: the display type at full strength, the structural devices that carry meaning, the signature motif, the density and pacing. A flat section is typically one that quietly opts out of the system's own strongest moves. The most reliable bolder pass brings the target up to the expressive level its neighbors already reach, in the system's own vocabulary rather than a new one. + +## The amplification + +- **Amplify what the system already owns.** Reuse its motif and its type scale at full strength, turned up for this section rather than invented for it. The bolder version should look more like the same brand, not less. +- **Keep content true.** Existing claims are part of the scope: preserve them unless the user supplies replacements. If real evidence is essential to the direction but absent, ask for it. +- **Commit, then clarify.** Half-measures read as noise. Make the one decisive move completely, then quiet everything around it so the move is legible. If every element got louder, the section got flatter. +- **Give it its own rhythm.** The target should read as a peak in the scroll, a shift in density or pace from what surrounds it, not simply more of the same. + +## The skeleton test + +Strip the copy out of your planned section and study the bare structure. Does the skeleton still say what this section is and why it matters, through hierarchy and the system's devices alone? If it only works once the words return, the boldness is in the text size, not the design. A placeholder for an image or artifact names a job, an anchor and a piece of evidence, not a cue to drop in a decorative photo; fill that job with whatever the subject actually has. + +## Before you finish + +- Everything outside the named target is unchanged. +- No new color, font, or system primitive appeared without being asked for. +- The conventions the section carried, including anything that drives an action, still work the same way. +- The section is unmistakably the same brand, only more sure of itself. + +When the target holds its own without pulling the page apart, hand off to `/impeccable polish` for the final pass. diff --git a/.config/opencode/skills/impeccable/reference/clarify.md b/.config/opencode/skills/impeccable/reference/clarify.md new file mode 100644 index 0000000..3047a9d --- /dev/null +++ b/.config/opencode/skills/impeccable/reference/clarify.md @@ -0,0 +1,94 @@ +> **Additional context needed**: audience knowledge and emotional state. + +Rewrite unclear interface text so users understand what happened, what matters, and what to do next. Preserve factual meaning, product terminology, and brand voice. + +## Audit the language + +Read the entire interaction path, not isolated strings. Identify: + +- ambiguous nouns, verbs, and actions; +- internal jargon or assumed knowledge; +- vague labels, outcomes, and system states; +- missing consequences, recovery, or timing; +- inconsistent terminology and capitalization; +- redundant headings, intros, helper text, and confirmations; +- text that breaks at realistic widths or in translation; +- tone that ignores stress, risk, success, or urgency. + +Infer audience and task from product context and surrounding UI. Ask before changing factual claims, legal meaning, or a term that may be domain-specific. + +## Set the message hierarchy + +For each state, decide: + +1. the one fact the user needs now; +2. the action available next; +3. supporting context that changes the decision; +4. the appropriate tone for this moment. + +Say each idea once. If the heading already explains the state, the introduction should add new information or disappear. + +## Rewrite by function + +### Actions and navigation + +Use a specific verb and object when the outcome is not already obvious. Labels should describe what will happen, not the gesture used to trigger it. Keep the same noun and verb for the same concept throughout the product. + +For destructive actions, name the object and consequence. Prefer undo over confirmation when recovery is safe. When confirmation is necessary, name the action on both the message and button instead of using `Yes`, `No`, `OK`, or `Submit`. + +### Forms + +Use persistent labels; placeholders are examples, not labels. Put format and eligibility requirements before submission. Explain why information is requested only when it is not obvious. Required and optional treatment should be consistent. + +Validation says what needs attention and how to correct it without blaming the user. Keep related instructions near the field and announce errors accessibly. + +### Errors and permissions + +An actionable error answers: + +1. what failed; +2. why, when known and useful; +3. how to recover or what alternative remains. + +Do not expose internal codes as the primary message. Do not promise a cause or resolution the system cannot know. Treat privacy, payment, deletion, access loss, and blocked work seriously; warmth is welcome, jokes are not. + +### Loading, empty, and success states + +Loading text names the real operation and sets an honest expectation when the wait is meaningful. Show determinate progress when available; never invent progress. + +An empty state distinguishes first use, no results, filters, permissions, and failure. Explain the state and provide the next useful action. + +Success confirms the completed outcome and mentions the next consequence only when it changes what the user should do. Routine success should be brief. + +### Help and instructional text + +Helper text answers an implicit question instead of restating the control. Use progressive disclosure for uncommon detail. Link text must make sense out of context; icon-only controls need accessible names. + +## Voice, accessibility, and localization + +Voice stays consistent; tone adapts to the moment. Use plain language without flattening terminology the audience genuinely knows. + +- Write complete translatable messages rather than concatenated fragments. +- Keep variables and numbers structured so translators can reorder them. +- Allow expansion instead of abbreviating prematurely. +- Make alt text convey the image's information; use empty alt for decoration. +- Keep screen-reader names aligned with visible labels and outcomes. +- Do not rely on punctuation, color, or iconography to carry the message alone. + +Maintain a short terminology glossary when inconsistency spans the product. Do not vary words for literary effect in an interface. + +## Verify + +Read the flow in context and test: + +- comprehension without hidden product knowledge; +- actionability at errors, empty states, and decision points; +- factual accuracy and consistent terminology; +- scanability at target widths and 200% zoom; +- long names, localization expansion, pluralization, and dynamic values; +- accessible names and announced state changes; +- tone appropriate to consequence and emotional context. + +The final copy is as short as it can be without removing meaning or recovery. + +When the language reads cleanly, hand off to `/impeccable polish` for the final pass. diff --git a/.config/opencode/skills/impeccable/reference/colorize.md b/.config/opencode/skills/impeccable/reference/colorize.md new file mode 100644 index 0000000..dc45f88 --- /dev/null +++ b/.config/opencode/skills/impeccable/reference/colorize.md @@ -0,0 +1,86 @@ +> **Additional context needed**: existing brand colors. + +Introduce color as hierarchy, meaning, and atmosphere. Preserve confirmed brand and semantic conventions; do not replace a visual world under the guise of colorizing it. + +--- + +## Visitor mode + +- **Persuade + Experience:** color may carry the voice and own large regions when the selected world calls for it. +- **Operate + Read:** color primarily encodes action, selection, status, wayfinding, and reading hierarchy. Rarity gives an accent force. + +## Audit before choosing + +Read DESIGN.md, tokens, assets, current themes, and representative states. Identify: + +- which colors are confirmed brand commitments; +- current surface, text, action, and semantic roles; +- places where grayscale obscures hierarchy or state; +- contrast failures and color-only communication; +- light/dark or data-visualization requirements; +- whether the task asks for more color or a new identity. + +If a new identity is required, use [new-work.md](new-work.md). Ask only when a binding brand decision cannot be inferred. + +## Choose a strategy + +Name the intended emotional temperature, dominant relationship, contrast range, and color dosage before editing. The strategy may be restrained or immersive; it must follow the brief and selected world rather than a fixed percentage rule. + +Build roles, not a bag of swatches: + +- canvas and elevated surfaces; +- primary and secondary text; +- action, focus, and selection; +- borders and separators; +- success, warning, error, and information; +- data categories or scales when needed. + +Use the project's existing color space. For a new web palette, prefer OKLCH because lightness and chroma can be adjusted predictably. Choose hue from product meaning and visual direction, never from a default category association. + +## Apply at system scale + +- Let the strongest color own a deliberate region or role instead of scattering tiny accents. +- Keep the primary action easy to find; do not spend its color on decoration. +- Tint neutrals only when the brand hue genuinely creates cohesion. Neutral gray is valid when it serves the world. +- On colored surfaces, derive secondary text from the foreground or surface hue rather than using washed-out generic gray. +- Keep semantic meanings consistent, but respect platform and domain conventions instead of assuming fixed hues. +- For data, use distinct lightness, chroma, shape, label, or pattern so color is not the only code. +- In dark mode, design surface elevation and contrast explicitly; do not invert the light theme mechanically. +- Define primitive values and semantic tokens when the project has a token system. Theme changes should normally remap semantic roles. + +Decoration without a relationship to hierarchy, state, content, or the visual world is not a color strategy. + +## Contrast and perception + +Verify computed foreground/background pairs: + +| Content | WCAG AA minimum | +|---|---| +| body text | 4.5:1 | +| large text | 3:1 | +| controls, icons, focus indicators | 3:1 | + +Do not rely on eyesight alone. Check interactive states, overlays, text on images, disabled content, and both themes. Simulate common vision deficiencies. Information conveyed by color also needs text, shape, iconography, or position. + +When deriving OKLCH ramps, vary lightness and reduce chroma near white and black. Do not keep high chroma at extreme lightness merely to make the math uniform. Prefer explicit colors over chains of translucent overlays when alpha would make contrast context-dependent. + +## Verify + +- Every color has a stable role or a world-specific atmospheric purpose. +- Attention lands on the intended action, content, or state. +- The palette works across quiet, dense, interactive, error, and empty states. +- Light and dark themes are each composed, not mechanically inverted. +- Contrast and non-color cues pass in all relevant states. +- The result is recognizably this product, not a generic “colorful” treatment. + +When the palette earns its place, hand off to `/impeccable polish` for the final pass. + +## Live-mode signature params + +When invoked from live mode, every variant declares a `color-amount` parameter. Author CSS against `var(--p-color-amount, 0.5)` so the user can move from neutral to the variant's full color strategy without regeneration. + +```json +{"id":"color-amount","kind":"range","min":0,"max":1,"step":0.05,"default":0.5,"label":"Color amount"} +``` + +Add at most two variant-specific parameters, such as palette, temperature, or tint behavior. Follow [live.md](live.md)'s parameter contract. diff --git a/.config/opencode/skills/impeccable/reference/craft-floor.md b/.config/opencode/skills/impeccable/reference/craft-floor.md new file mode 100644 index 0000000..9653b98 --- /dev/null +++ b/.config/opencode/skills/impeccable/reference/craft-floor.md @@ -0,0 +1,44 @@ +# Craft floor + +Load this after the direction is settled, and build without announcing the checklist. A pinned brief or the committed visual world overrides anything here; your own habit does not. When the design hook is active it already enforces the mechanical checks below as you edit: act on its findings instead of re-auditing each rule. + +## Verify + +Each of these is a check on the built result, not an intention. Run them together in the batched inspection rounds, not as separate screenshot trips; the checks share one render. + +- **Contrast:** body and placeholder text ≥4.5:1, large text ≥3:1. On colored surfaces tint secondary text from that hue or the foreground; never gray. +- **Depth:** shadows carry an offset and a soft blur. A zero-offset colored halo is decoration. +- **Spacing:** tight groups, generous separation, more space above a heading than below it. Read the computed values. +- **Type:** body measure 65–75ch, display max 6rem, tracking floor -0.04em, balanced headings, obvious scale and weight steps. Run the real copy at every breakpoint and fix what overflows. +- **Motion:** one authored moment, not scattered effects and not one identical entrance on every section. Exponential ease-out from an already-visible default. Reach past transform and opacity: blur, backdrop-filter, clip-path, mask, and shadow belong to the palette when they stay smooth. +- **States:** hover, disabled, loading, error, empty. Plus real content, working controls, responsive composition, keyboard focus. +- **Browser surfaces:** the parts you did not draw still carry the design. Text selection, the caret, custom scrollbars, focus rings, underline offset, and the numerals in tabular data all ship with browser defaults that belong to no design system. Theme them from the palette. This is the cheapest signal that a page was built rather than assembled, and the one models skip most reliably. +- **Copy:** the product's own language. Controls name their action; errors name the problem and the recovery. +- **Coverage:** every brief requirement present and findable within seconds. + +## Refuse + +These are the category's defaults, not bans: the brief's own words can earn any of them. Reaching for one when the axis is free means you were not deciding; recognizing that means rewriting the element, not softening it. + +Page scaffolds: + +- Same-size cards of icon plus heading plus text as the page structure. Cards are the lazy container; nested cards are always wrong. +- The hero-metric template: big number, small label, supporting stats, accent. +- A kicker or eyebrow above a heading. This one is a ban, not a default: no brief earns it back. The heading carries its own weight; delete the label and let the heading speak. +- Section numbers (01 / 02 / 03) unless the sequence itself carries information the reader needs. +- A modal for a task that needs neither interruption nor protected focus. + +Surface habits: + +- Gradient text. Emphasis comes from weight or size. +- Glass and blur as decoration rather than as a specific effect. +- A colored `border-left` or `border-right` above 1px on cards, list items, callouts, or alerts. +- Hard offset shadows (`box-shadow: 4px 4px 0`) outside a world that is actually neobrutalist. The zero-blur block shadow is a costume, not a depth system; a world that did not choose it never earns it as a default. +- Sparklines, progress rings, and soft-shadowed rounded rectangles standing in for content. +- Monospace as a costume for "technical" rather than for code, data, or measurement. +- A system display face (Impact, Arial Black, the platform sans) as the display voice of an own-world page. Source and self-host a face whose character matches the approved lettering; the closest installed font is a failure, not a fallback. +- Unicode glyphs or emoji standing in for an icon system. Icons are drawn, from a real library or authored SVG, in one consistent stroke and weight. +- Geometric masks standing in for organic contours. A circle, polygon, or radial-gradient cutout approximating a photographic subject's edge is the cheap version of the effect and reads worse than omitting it. Derive an alpha matte from the actual image, or produce a cut-out asset. +- Light or dark picked by category. Pick it from the use scene: who, where, under what ambient light. + +The floor holds the mechanics; it never picks the direction. With every check green, spend the page on the committed world, and when torn between refined and committed, commit. diff --git a/.config/opencode/skills/impeccable/reference/craft.md b/.config/opencode/skills/impeccable/reference/craft.md new file mode 100644 index 0000000..dbbc940 --- /dev/null +++ b/.config/opencode/skills/impeccable/reference/craft.md @@ -0,0 +1,5 @@ +# Craft (deprecated alias) + +`craft` is a deprecated alias for an ordinary request to make new visual work. It adds no setup, interview, checkpoint, tool, or quality behavior. Apply SKILL.md's normal routing: create missing PRODUCT.md through [init.md](init.md), then follow [new-work.md](new-work.md) for visual authority, world and surface decisions, implementation, and finish. + +Do not tell users they need to invoke `craft`. Natural requests such as “build this feature,” “make a landing page,” or “redesign this screen” use the same flow. diff --git a/.config/opencode/skills/impeccable/reference/critique.md b/.config/opencode/skills/impeccable/reference/critique.md new file mode 100644 index 0000000..d3a9362 --- /dev/null +++ b/.config/opencode/skills/impeccable/reference/critique.md @@ -0,0 +1,806 @@ +### Purpose + +Resolve one stable target, run two independent assessments, synthesize a design critique, persist a snapshot, and ask the user what to improve next. The chat response is the primary deliverable; the snapshot is an archive of that run. + +### Hard Invariants + +- Assessment A (design review) and Assessment B (detector/browser evidence) are both required. +- Assessment A and B MUST run as two isolated sub-agents whenever a sub-agent/Task tool is exposed. Running them inline in this context is "possible" but is NOT permitted; it is a degraded run. Inline is allowed ONLY when no sub-agent tool exists (or the user declined, on harnesses that ask). +- If you degrade for any reason, the report's first line MUST be a banner: `⚠️ DEGRADED: single-context ()`. A silent degraded critique is a failed critique. +- Assessment A must finish before detector findings enter the parent synthesis context. Detector output is deterministic, but it still anchors judgment. +- A skipped detector is a failed critique run unless `impeccable detect` is missing or crashes after a real attempt. +- Viewable targets require browser inspection when available. +- Any local server started only for critique visualization must run in the background, have a recorded stop method, and be stopped before final reporting unless the user asks to keep it. +- Do not claim a user-visible overlay exists unless script injection succeeded and the detector ran in the page. +- The question is the LAST thing in the response. Write the entire report out first, then ask; nothing follows the question. Prose emitted after a structured question is withheld until the user answers it, so a report written after the question reads as if the critique never ran. +- A run that ends with neither the targeted questions nor a literal `Questions skipped: ` line is an incomplete run. The report is not the finish; the close is. + +### Setup + +1. **Resolve the target** to a concrete file path or URL. Prefer a source path over a dev-server URL when both identify the same surface; ports drift, paths do not. + - "the homepage" -> `site/pages/index.astro` or `index.html` + - "the settings modal" -> the primary component file + - "this page" -> the current URL or source file +2. **Confirm the target slugs cleanly**: + ```bash + .opencode/skills/impeccable/scripts/impeccable critique-storage slug "" + ``` + Every later command also accepts the resolved target directly and derives the same slug internally; never hand-write a slug. If this exits non-zero, skip persistence and trend for this run, but continue the critique. +3. **Read `.impeccable/critique/ignore.md`** if it exists. Drop matching findings silently; it is the only prior-run input critique consumes. + +### Assessment Orchestration + +Delegate Assessment A and Assessment B to separate sub-agents. They must not see each other's output. Do not show findings to the user until synthesis. + +Sub-agent gate (all harnesses): +- Unless a harness-specific gate below overrides this, spawn A and B as two isolated, parallel sub-agents whenever a sub-agent/Task tool is exposed. This is the default and is mandatory; do not run them inline because it is faster. +- "Unavailable" means exactly one thing: no sub-agent/Task tool is exposed in this session (or, on harnesses that ask, the user declined). It does not mean inconvenient. +- If and only if sub-agents are unavailable, fall back sequentially: finish and record Assessment A, then run Assessment B, then synthesize, and emit the degraded banner. +- Whichever path you take, declare it in the report header (see Report header provenance). Skipping sub-agents without the banner is the most common failure of this command. + +If browser automation is available, each assessment creates its own new tab. Never reuse an existing tab, even if it is already at the right URL. + +### Assessment A: Design Review + +Read relevant source files and visually inspect the live page when browser automation is available. Think like a design director. + +Evaluate: +- **Design specificity**: Is the composition, interaction, and visual language grounded in this product, or could an unrelated product use it unchanged? Make this judgment before seeing detector output. +- **Holistic design**: hierarchy, IA, emotional fit, discoverability, composition, typography, color, accessibility, states, copy, and edge cases. +- **Cognitive load**: consult the [Cognitive Load Assessment](#cognitive-load-assessment) section below; report checklist failures and decision points with >4 visible options. +- **Emotional journey**: peak-end rule, emotional valleys, reassurance at high-stakes moments. +- **Nielsen heuristics**: consult the [Heuristics Scoring Guide](#heuristics-scoring-guide) section below; score all 10 heuristics 0-4, marking any heuristic the mode-applicability rule allows as `n/a` instead of forcing a number. + +Return: design-specificity verdict, heuristic scores, cognitive load, emotional journey, 2-3 strengths, 3-5 priority issues, persona red flags, minor observations, and provocative questions. + +### Assessment B: Detector + Browser Evidence + +Run the bundled detector and browser visualization evidence. Assessment B is mandatory and must remain isolated from Assessment A until both are complete. + +CLI scan: +```bash +.opencode/skills/impeccable/scripts/impeccable detect --json [target] +``` + +- Pass markup files/directories as `[target]`; do not pass CSS-only files. +- For URLs, skip CLI scan and use browser visualization. +- For very large trees (500+ scannable files), narrow scope or ask. +- Exit code 0 = clean; 2 = findings. +- If the detector entrypoint is missing or fails to load, report deterministic scan unavailable and continue with browser/manual review. + +Browser visualization is required for a viewable target when browser automation is available. Use a localhost dev/static URL for local files; avoid `file://` unless the available browser explicitly supports this workflow. Overlay flow: + +1. Create a fresh tab and navigate. Prefer the harness's native/browser-canvas screenshot path before hand-rolling a Playwright/Puppeteer script; only fall back to a custom script when no native browser tool is exposed. +2. Preflight mutable injection by setting `document.title` and appending a `