diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 4a05408a8..4f538a6f8 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -21,6 +21,7 @@ assignees: '' - [ ] Gemini CLI - [ ] Codex CLI - [ ] VS Code Copilot +- [ ] Grok Build - [ ] Kiro - [ ] OpenCode - [ ] Qoder diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 46af5d640..3ebd0079c 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -26,7 +26,7 @@ This repo is issue-first for outside contributions. If you are not `pbakaus` or - [ ] Source files updated in `source/` - [ ] `bun run build` ran successfully - [ ] `bun test` passes -- [ ] Tested with at least one provider (Cursor / Claude Code / Gemini CLI / Codex / Copilot / Kiro / OpenCode / Qoder / Mistral Vibe) +- [ ] Tested with at least one provider (Cursor / Claude Code / Gemini CLI / Codex / Copilot / Grok Build / Kiro / OpenCode / Qoder / Mistral Vibe) - [ ] README / DEVELOP.md updated if needed - [ ] I reviewed the full diff myself before requesting human review - [ ] I disclosed any AI assistance in this PR and related commits/comments, or no AI assistance was used diff --git a/.github/workflows/sync-generated-output.yml b/.github/workflows/sync-generated-output.yml index 44bf72283..76741b03e 100644 --- a/.github/workflows/sync-generated-output.yml +++ b/.github/workflows/sync-generated-output.yml @@ -26,6 +26,7 @@ env: .cursor .gemini .github/skills + .grok .kiro .opencode .pi diff --git a/AGENTS.md b/AGENTS.md index 124e1b491..1223f901f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -21,7 +21,7 @@ Run `bun run build` after changing anything in `skill/`, transformer code, or us ## Generated Provider Output Policy -The root harness folders (`.agents/skills/`, `.claude/skills/`, `.cursor/skills/`, `.gemini/skills/`, `.github/skills/`, `.kiro/skills/`, `.opencode/skills/`, `.pi/skills/`, `.qoder/skills/`, `.rovodev/skills/`, `.trae*/skills/`, `.vibe/skills/`) and `plugin/` stay tracked so `main` remains installable for direct GitHub, `npx skills`, and submodule users. They are still generated artifacts. +The root harness folders (`.agents/skills/`, `.claude/skills/`, `.cursor/skills/`, `.gemini/skills/`, `.github/skills/`, `.grok/skills/`, `.kiro/skills/`, `.opencode/skills/`, `.pi/skills/`, `.qoder/skills/`, `.rovodev/skills/`, `.trae*/skills/`, `.vibe/skills/`) and `plugin/` stay tracked so `main` remains installable for direct GitHub, `npx skills`, and submodule users. They are still generated artifacts. Normal development should be source-first: stage changes in `skill/`, `scripts/`, `cli/`, `site/`, `extension/`, `functions/`, and `tests/`; leave generated harness churn unstaged unless the user asked for it. After source changes land on `main`, `.github/workflows/sync-generated-output.yml` runs `bun run build:release` and commits generated provider output directly back to `main`. Treat generated harness diffs as release artifacts and keep them out of feature PRs unless they are the point of the PR. diff --git a/README.md b/README.md index 7cc404139..2b18b4c4f 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ From the root of your project, run: npx impeccable install ``` -This shows the harness folders it detected (for example `~/.claude`, `~/.codex`, or project-local `.cursor`), lets you keep the detected set or customize providers, then asks whether to install into the current project or globally. Use `--providers=claude,codex,cursor` and `--scope=project|global` to skip those choices in scripts. On Claude Code, Cursor, and Codex, it also installs the provider-native hook manifest for the current project. Works with Cursor, Claude Code, Gemini CLI, Codex CLI, Grok Build, and every other supported tool. Reload your harness afterward. +This shows the harness folders it detected (for example `~/.claude`, `~/.codex`, `~/.grok`, or project-local `.cursor`), lets you keep the detected set or customize providers, then asks whether to install into the current project or globally. Use `--providers=claude,codex,cursor,grok` and `--scope=project|global` to skip those choices in scripts. On Claude Code, Cursor, Codex, GitHub Copilot, and Grok Build, it also installs the provider-native hook manifest for the current project. Works with Cursor, Claude Code, Gemini CLI, Codex CLI, Grok Build, and every other supported tool. Reload your harness afterward. To refresh an existing install, run: @@ -111,7 +111,7 @@ To refresh an existing install, run: npx impeccable update ``` -Codex users should open `/hooks` after install or update and approve the project hook when prompted. Codex tracks trust by hook definition, so updates that change `.codex/hooks.json` can require approval again. +Codex users should open `/hooks` after install or update and approve the project hook when prompted. Codex tracks trust by hook definition, so updates that change `.codex/hooks.json` can require approval again. Grok Build users need project folder trust (`/hooks-trust` or launch with `--trust`) before `.grok/hooks/` scripts run. ### Option 2: Git Submodule @@ -124,7 +124,7 @@ git add .gitmodules .impeccable .claude .cursor git commit -m "Add Impeccable skills" ``` -Use the providers your project needs, for example `claude`, `cursor`, `gemini`, `codex`, `github`, `opencode`, `pi`, `qoder`, `trae`, `trae-cn`, `rovo-dev`, or `vibe`. The command links individual skill folders from `.impeccable/dist/universal/` and leaves existing real skill directories untouched unless you pass `--force`. +Use the providers your project needs, for example `claude`, `cursor`, `gemini`, `codex`, `github`, `grok`, `opencode`, `pi`, `qoder`, `trae`, `trae-cn`, `rovo-dev`, or `vibe`. The command links individual skill folders from `.impeccable/dist/universal/` and leaves existing real skill directories untouched unless you pass `--force`. To update later: @@ -144,10 +144,10 @@ npx impeccable link --source=.impeccable --providers=claude,cursor **Grok Build:** ```bash -grok plugin install pbakaus/impeccable --trust +grok plugin install pbakaus/impeccable#plugin --trust ``` -> Grok Build only. Then run `/impeccable init` in a Grok session. +> Grok Build only. The `#plugin` suffix installs the slim plugin package (skills, agents, and hooks) instead of the full monorepo. Then run `/impeccable init` in a Grok session. Project-scoped installs via `npx impeccable install --providers=grok` also work and write `.grok/skills/` plus `.grok/hooks/impeccable.json`. ### Option 4: Download from Website @@ -259,6 +259,17 @@ cp -r dist/vibe/.vibe your-project/ cp -r dist/vibe/.vibe/skills/* ~/.vibe/skills/ ``` +**Grok Build:** +```bash +# Project-specific +cp -r dist/grok/.grok your-project/ + +# Or global (applies to all projects) +cp -r dist/grok/.grok/skills/* ~/.grok/skills/ +``` + +> Prefer `npx impeccable install --providers=grok` or `grok plugin install pbakaus/impeccable#plugin --trust` so the design hook installs too. Project hooks need `/hooks-trust` (or `--trust`) once per folder. + ## Usage Once installed, every command runs through the single `/impeccable` skill: @@ -325,7 +336,7 @@ If an ephemeral file (a screenshot, `config.local.json`) was committed before yo ## Design hook -On Claude Code, GitHub Copilot, Codex, and Cursor, `npx impeccable install` and `npx impeccable update` install a provider-native hook manifest along with the skill payload. The hook runs the Impeccable design detector on direct UI file edits and surfaces findings back into the agent flow. Claude Code, GitHub Copilot, and Codex surface findings after the edit. Cursor blocks bad proposed writes before they land. +On Claude Code, GitHub Copilot, Codex, Cursor, and Grok Build, `npx impeccable install` and `npx impeccable update` install a provider-native hook manifest along with the skill payload. The hook runs the Impeccable design detector on direct UI file edits and surfaces findings back into the agent flow. Claude Code, GitHub Copilot, Codex, and Grok Build surface findings after the edit (and run a deeper pass on Stop where supported). Cursor blocks bad proposed writes before they land. Installed hook surfaces: diff --git a/cli/bin/commands/skills.mjs b/cli/bin/commands/skills.mjs index 0b2b10b2a..0af25cab5 100644 --- a/cli/bin/commands/skills.mjs +++ b/cli/bin/commands/skills.mjs @@ -23,7 +23,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url)); const API_BASE = 'https://impeccable.style'; // Provider folder names in project roots -const PROVIDER_DIRS = ['.claude', '.cursor', '.gemini', '.agents', '.github', '.kiro', '.opencode', '.pi', '.qoder', '.trae', '.trae-cn', '.rovodev', '.vibe']; +const PROVIDER_DIRS = ['.claude', '.cursor', '.gemini', '.agents', '.github', '.grok', '.kiro', '.opencode', '.pi', '.qoder', '.trae', '.trae-cn', '.rovodev', '.vibe']; const PROVIDER_ALIASES = { agents: '.agents', claude: '.claude', @@ -33,6 +33,9 @@ const PROVIDER_ALIASES = { cursor: '.cursor', gemini: '.gemini', github: '.github', + grok: '.grok', + 'grok-build': '.grok', + xai: '.grok', kiro: '.kiro', opencode: '.opencode', pi: '.pi', @@ -50,6 +53,7 @@ const PROVIDER_DISPLAY = { '.cursor': { name: 'Cursor', input: 'cursor' }, '.gemini': { name: 'Gemini CLI', input: 'gemini' }, '.github': { name: 'GitHub Copilot', input: 'github' }, + '.grok': { name: 'Grok Build', input: 'grok' }, '.kiro': { name: 'Kiro', input: 'kiro' }, '.opencode': { name: 'OpenCode', input: 'opencode' }, '.pi': { name: 'Project Indigo', input: 'pi' }, @@ -59,7 +63,7 @@ const PROVIDER_DISPLAY = { '.trae-cn': { name: 'Trae CN', input: 'trae-cn' }, '.vibe': { name: 'Mistral Vibe', input: 'vibe' }, }; -const PROVIDER_INPUT_ORDER = ['claude', 'codex', 'cursor', 'gemini', 'github', 'kiro', 'opencode', 'pi', 'qoder', 'trae', 'trae-cn', 'rovo-dev', 'vibe']; +const PROVIDER_INPUT_ORDER = ['claude', 'codex', 'cursor', 'gemini', 'github', 'grok', 'kiro', 'opencode', 'pi', 'qoder', 'trae', 'trae-cn', 'rovo-dev', 'vibe']; // Providers whose GLOBAL (home) skills dir is not `/skills`. // Pi discovers global skills from ~/.pi/agent/skills/; project scope @@ -76,6 +80,7 @@ const GLOBAL_HARNESS_HINTS = [ { home: '.codex', provider: '.agents' }, { home: '.cursor', provider: '.cursor' }, { home: '.gemini', provider: '.gemini' }, + { home: '.grok', provider: '.grok' }, { home: '.kiro', provider: '.kiro' }, { home: '.opencode', provider: '.opencode' }, { home: '.pi', provider: '.pi' }, @@ -120,6 +125,12 @@ const PROVIDER_HOOK_ARTIFACTS = { '.github': [ { sourceProvider: '.github', rel: 'hooks/impeccable.json', destProvider: '.github' }, ], + // Grok Build discovers project hooks from `.grok/hooks/*.json`. Team-shared + // by default (commit them if the whole team uses Grok); folder trust is still + // required via `/hooks-trust` or `--trust` before they run. + '.grok': [ + { sourceProvider: '.grok', rel: 'hooks/impeccable.json', destProvider: '.grok' }, + ], }; function userProviderSkillsDir(home, provider) { @@ -571,7 +582,7 @@ async function copyOrExtractLocalBundle(sourceValue) { */ function normalizeForHash(content) { return content - .replace(/\.(claude|cursor|agents|github|gemini|codex|kiro|opencode|pi|qoder|trae|trae-cn|rovodev|vibe)\/skills\//g, '.PROVIDER/skills/'); + .replace(/\.(claude|cursor|agents|github|gemini|codex|grok|kiro|opencode|pi|qoder|trae|trae-cn|rovodev|vibe)\/skills\//g, '.PROVIDER/skills/'); } function hashSkillFile(filePath) { diff --git a/cli/lib/download-providers.js b/cli/lib/download-providers.js index 3e73ec4b6..f1fcfcb02 100644 --- a/cli/lib/download-providers.js +++ b/cli/lib/download-providers.js @@ -5,6 +5,7 @@ export const FILE_DOWNLOAD_PROVIDER_CONFIG_DIRS = Object.freeze({ codex: '.codex', agents: '.agents', github: '.github', + grok: '.grok', kiro: '.kiro', opencode: '.opencode', pi: '.pi', diff --git a/docs/DEVELOP.md b/docs/DEVELOP.md index 59675c12f..f7b37b2cd 100644 --- a/docs/DEVELOP.md +++ b/docs/DEVELOP.md @@ -170,6 +170,8 @@ The skill-behavior suite runs three providers (claude-haiku-4-5, gpt-5.4-mini, g - [Pi Skills](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/docs/skills.md) - [Qoder Skills](https://docs.qoder.com/extensions/skills) - [Mistral Vibe Skills](https://docs.mistral.ai/vibe/code/cli/skills) +- [Grok Build Skills, Plugins & Marketplaces](https://docs.x.ai/build/features/skills-plugins-marketplaces) +- [Grok Build Hooks](https://docs.x.ai/build/features/hooks) ## Repository Structure diff --git a/docs/HARNESSES.md b/docs/HARNESSES.md index c74fbc0a3..e8183319a 100644 --- a/docs/HARNESSES.md +++ b/docs/HARNESSES.md @@ -3,7 +3,7 @@ Source of truth for what each AI coding harness supports in terms of agent skills. Used to inform provider configs in `scripts/lib/transformers/providers.js`. -Last verified: 2026-04-28 (subagent landscape spot-checked 2026-06-28; Mistral Vibe row verified 2026-07-16) +Last verified: 2026-04-28 (subagent landscape spot-checked 2026-06-28; Mistral Vibe row verified 2026-07-16; Grok Build row verified 2026-07-21) > This file is point-in-time. Capabilities move fast; verify live before relying > on any "only X supports Y" claim. Notably, the subagent table below lists @@ -25,6 +25,7 @@ Last verified: 2026-04-28 (subagent landscape spot-checked 2026-06-28; Mistral V | Trae | TBD (no official skills docs found yet) | | Rovo Dev | https://support.atlassian.com/rovo/docs/extend-rovo-dev-cli-with-agent-skills | | Mistral Vibe | https://docs.mistral.ai/vibe/code/cli/skills | +| Grok Build | https://docs.x.ai/build/features/skills-plugins-marketplaces | ## Spec Compliance @@ -36,27 +37,28 @@ Provider-specific extensions beyond the spec: `user-invocable`, `argument-hint`, Fields marked with * are spec-standard. Others are provider extensions. -| Field | Claude Code | Cursor | Gemini | Codex | Copilot | Kiro | OpenCode | Pi | Qoder | Rovo Dev | Mistral Vibe | -|-------|:-----------:|:------:|:------:|:-----:|:-------:|:----:|:--------:|:--:|:-----:|:--------:|:------------:| -| `name`* | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | -| `description`* | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | -| `license`* | Yes | Yes | Ignored | No | Yes | Yes | Yes | Yes | Yes | Yes | Yes | -| `compatibility`* | Yes | Yes | Ignored | No | Yes | Yes | Yes | Yes | Yes | Yes | Yes | -| `metadata`* | Yes | Yes | Ignored | No | Yes | Yes | Yes | Yes | Yes | Yes | Yes | -| `allowed-tools`* | Yes | No | Ignored | No | No | No | Yes | Yes | Yes | Yes | Yes | -| `user-invocable` | Yes | No | No | No | Yes | No | Yes | No | Yes | Yes | Yes | -| `argument-hint` | Yes | No | No | No | Yes | No | Yes | No | Yes | Yes | No | -| `disable-model-invocation` | Yes | Yes | No | No | Yes | No | Yes | Yes | TBD | TBD | No | -| `model` | Yes | No | No | No | No | No | Yes | No | No | No | No | -| `effort` | Yes | No | No | No | No | No | No | No | No | No | No | -| `context` | Yes | No | No | No | No | No | No | No | No | No | No | -| `agent` | Yes | No | No | No | No | No | Yes | No | No | No | No | -| `hooks` | Yes | No | No | Yes | No | No | No | No | No | No | No | +| Field | Claude Code | Cursor | Gemini | Codex | Copilot | Grok | Kiro | OpenCode | Pi | Qoder | Rovo Dev | Mistral Vibe | +|-------|:-----------:|:------:|:------:|:-----:|:-------:|:----:|:----:|:--------:|:--:|:-----:|:--------:|:------------:| +| `name`* | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | +| `description`* | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | +| `license`* | Yes | Yes | Ignored | No | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | +| `compatibility`* | Yes | Yes | Ignored | No | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | +| `metadata`* | Yes | Yes | Ignored | No | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | +| `allowed-tools`* | Yes | No | Ignored | No | No | Yes | No | Yes | Yes | Yes | Yes | Yes | +| `user-invocable` | Yes | No | No | No | Yes | Yes | No | Yes | No | Yes | Yes | Yes | +| `argument-hint` | Yes | No | No | No | Yes | Yes | No | Yes | No | Yes | Yes | No | +| `disable-model-invocation` | Yes | Yes | No | No | Yes | Yes | No | Yes | Yes | TBD | TBD | No | +| `model` | Yes | No | No | No | No | Yes | No | Yes | No | No | No | No | +| `effort` | Yes | No | No | No | No | Yes | No | No | No | No | No | No | +| `context` | Yes | No | No | No | No | No | No | No | No | No | No | No | +| `agent` | Yes | No | No | No | No | No | No | Yes | No | No | No | No | +| `hooks` | Yes | No | No | Yes | No | Yes | No | No | No | No | No | No | Notes: - Gemini CLI validates only `name` and `description`; other spec fields are parsed but ignored. - Codex CLI uses a separate `agents/openai.yaml` sidecar for skill metadata (icons, branding, MCP tools, invocation control). Codex also auto-discovers subagents bundled inside an installed skill's `agents/` folder (TOML), which is how Impeccable ships its asset-producer. Standalone custom agents can still live under `.codex/agents/` or `~/.codex/agents/`, but Impeccable no longer installs anything there. - Codex CLI hooks ship under `[features].hooks = true` (still flagged), require `/hooks` trust ceremony per-update, and are disabled on Windows. +- Grok Build is Claude Code compatible with zero config: it also reads `.claude/skills/`, `.claude/settings.json` hooks, and Claude plugin layouts. Native paths are `.grok/skills/`, `.grok/hooks/*.json`, and `.grok/agents/`. Skill frontmatter supports `when-to-use` in addition to the fields above. Project hooks require `/hooks-trust` (or `--trust`). See https://docs.x.ai/build/features/skills-plugins-marketplaces and https://docs.x.ai/build/features/hooks. - Kiro recognizes `user-invocable` and `disable-model-invocation` per community reports but does not formally document them. - Unknown fields are silently ignored by all harnesses. @@ -67,6 +69,7 @@ Notes: | Claude Code | Yes (`PostToolUse`) | No | `.claude/settings.json` | Project-local settings entry installed by `npx impeccable skills install/update`. Runs `.claude/skills/impeccable/scripts/hook.mjs`. | | Codex CLI | Yes (`PostToolUse`) | No | `.codex/hooks.json` | Project-local manifest installed with the `.agents/skills/impeccable` payload. Runs `.agents/skills/impeccable/scripts/hook.mjs` from the git root. Requires normal `/hooks` trust approval. | | Cursor | Yes (`preToolUse`) | No | `.cursor/hooks.json` | Project-level manifest installed with `.cursor/skills/impeccable`. Runs `hook-before-edit.mjs` to block bad proposed writes before they land. Reloads on save; restart Cursor if hooks do not pick up. | +| Grok Build | Yes (`PostToolUse`) | No | `.grok/hooks/impeccable.json` | Project-local manifest installed with `.grok/skills/impeccable`. Claude-compatible matchers (`Edit\|Write\|MultiEdit`) alias to Grok tools. Also runs a Stop deep pass. Requires `/hooks-trust` or `--trust`. Plugin installs use `plugin/hooks/hooks.json` with `${CLAUDE_PLUGIN_ROOT}` (aliased to `GROK_PLUGIN_ROOT`). | | All other harnesses | No | No | n/a | No documented hook surface today. Skill and commands still ship. | ## Skill Directory Structure @@ -86,6 +89,7 @@ Notes: | Trae International | `.trae/skills/` | TBD | | Rovo Dev | `.rovodev/skills/` | `~/.rovodev/skills/` (user-level) | | Mistral Vibe | `.vibe/skills/` (project), `~/.vibe/skills/` (global) | `.agents/skills/` (project), `~/.agents/skills/` (global) | +| Grok Build | `.grok/skills/` (project), `~/.grok/skills/` (global) | `.agents/skills/`, `.claude/skills/`, `.cursor/skills/` (Claude/Cursor compat, configurable) | All harnesses support the `{skill-name}/SKILL.md` directory structure with optional `reference/`, `scripts/`, and `assets/` subdirectories. @@ -100,15 +104,17 @@ All harnesses support the `{skill-name}/SKILL.md` directory structure with optio | Harness | Native directory | File format | |---------|------------------|-------------| | Claude Code | `.claude/agents/` (installed plugin) | Markdown with YAML frontmatter | +| Grok Build | `.grok/agents/` (project) and plugin `agents/` | Markdown with YAML frontmatter (Claude-compatible) | | Codex CLI | `/agents/` (nested, auto-discovered) | TOML | -Impeccable keeps canonical agent prompts under `skill/agents/` and emits provider-native files only for harnesses with a documented on-disk subagent format. Claude reads its agents from the installed plugin; Codex auto-discovers the TOML bundled inside the installed skill's own `agents/` folder, so the normal skills install carries it with no separate sidecar. +Impeccable keeps canonical agent prompts under `skill/agents/` and emits provider-native files only for harnesses with a documented on-disk subagent format. Claude reads its agents from the installed plugin; Grok reads the same markdown agents from the plugin package and from project `.grok/agents/`; Codex auto-discovers the TOML bundled inside the installed skill's own `agents/` folder, so the normal skills install carries it with no separate sidecar. **Spawn / permission model** (matters more than directory support when building skills): | Harness | Who can spawn a subagent | |---------|--------------------------| | Claude Code | Programmatically, from within the skill/agent flow. | +| Grok Build | Programmatically via `spawn_subagent` (built-in types plus project/user agents under `.grok/agents/`). | | Codex CLI | Only if the user has allowed sub-agents / parallel work; otherwise the skill must ask once, then stop (see `skill/reference/critique.md` `` gate). | | Cursor | Agent-chosen: auto-delegated by the Agent, or user-invoked via `/name`. Not reliably skill-spawnable. | | Others | Varies; treat as unavailable unless verified, and degrade loudly. | diff --git a/scripts/build.js b/scripts/build.js index dc9848039..de3ff3e60 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -462,11 +462,15 @@ This folder contains skills for all supported tools: .codex/ -> Codex custom agents (Codex skills use .agents/) .agents/ -> Codex CLI .github/ -> GitHub Copilot + .grok/ -> Grok Build .kiro/ -> Kiro .opencode/ -> OpenCode .pi/ -> Pi .trae-cn/ -> Trae China .trae/ -> Trae International + .rovodev/ -> Rovo Dev + .vibe/ -> Mistral Vibe + .qoder/ -> Qoder To install, copy the relevant folder(s) into your project root. For Codex, repo and user skill installs come from .agents/skills. @@ -590,18 +594,21 @@ async function build() { console.log(`📋 Synced skills to: ${syncConfigs.map(p => p.configDir).join(', ')}`); - // Build the Claude Code plugin subtree at ./plugin/. - // The Claude Code marketplace is configured with `source: "./plugin"`, so - // the plugin cache only copies this slim directory (~0.3 MB) instead of - // the entire monorepo (~291 MB on the previous "./" source). The harness - // dirs above stay where they are because `npx skills add pbakaus/impeccable` - // reads them directly from the GitHub repo at install time. + // Build the shared plugin subtree at ./plugin/. + // Claude Code marketplace is configured with `source: "./plugin"`, so the + // plugin cache only copies this slim directory (~0.3 MB) instead of the + // entire monorepo. Grok Build installs the same subtree via + // `grok plugin install pbakaus/impeccable#plugin --trust` (or the + // marketplace source). The harness dirs above stay where they are because + // `npx skills add pbakaus/impeccable` reads them from the GitHub repo. const pluginRoot = path.join(ROOT_DIR, 'plugin'); const pluginManifestDir = path.join(pluginRoot, '.claude-plugin'); + const grokPluginManifestDir = path.join(pluginRoot, '.grok-plugin'); const pluginSkillsDir = path.join(pluginRoot, 'skills'); const pluginAgentsDir = path.join(pluginRoot, 'agents'); const pluginHooksDir = path.join(pluginRoot, 'hooks'); if (fs.existsSync(pluginManifestDir)) fs.rmSync(pluginManifestDir, { recursive: true }); + if (fs.existsSync(grokPluginManifestDir)) fs.rmSync(grokPluginManifestDir, { recursive: true }); if (fs.existsSync(pluginSkillsDir)) fs.rmSync(pluginSkillsDir, { recursive: true }); if (fs.existsSync(pluginAgentsDir)) fs.rmSync(pluginAgentsDir, { recursive: true }); if (fs.existsSync(pluginHooksDir)) fs.rmSync(pluginHooksDir, { recursive: true }); @@ -636,6 +643,26 @@ async function build() { JSON.stringify(pluginManifest, null, 2) + '\n', ); + // Native Grok plugin manifest. Grok also reads `.claude-plugin/`; dual + // manifests keep both marketplaces and `grok plugin validate` happy when + // Claude compat is disabled. + // https://docs.x.ai/build/features/skills-plugins-marketplaces + const grokPluginManifest = { + name: pluginManifest.name, + version: pluginManifest.version, + description: pluginManifest.description, + author: pluginManifest.author, + homepage: pluginManifest.homepage, + repository: pluginManifest.repository, + license: pluginManifest.license || 'MIT', + keywords: ['design', 'frontend', 'ui', 'ux', 'skills', 'hooks'], + }; + fs.mkdirSync(grokPluginManifestDir, { recursive: true }); + fs.writeFileSync( + path.join(grokPluginManifestDir, 'plugin.json'), + JSON.stringify(grokPluginManifest, null, 2) + '\n', + ); + const claudeSkillsSrc = path.join(DIST_DIR, 'claude-code', '.claude', 'skills', 'impeccable'); if (fs.existsSync(claudeSkillsSrc)) { fs.mkdirSync(pluginSkillsDir, { recursive: true }); @@ -646,17 +673,18 @@ async function build() { copyDirSync(claudeAgentsSrc, pluginAgentsDir); } - // Ship the design detector as a plugin-packaged hook. Claude Code - // auto-discovers `hooks/hooks.json` at the plugin root, so marketplace / - // `/plugin install` users get the PostToolUse hook without it being merged - // into their project `.claude/settings.json` (that path is the CLI's job). + // Ship the design detector as a plugin-packaged hook. Claude Code and + // Grok Build both auto-discover `hooks/hooks.json` at the plugin root + // (Grok aliases CLAUDE_PLUGIN_ROOT → GROK_PLUGIN_ROOT), so marketplace / + // plugin-install users get PostToolUse + Stop without merging into project + // settings (that path remains the CLI's job for project-scoped installs). fs.mkdirSync(pluginHooksDir, { recursive: true }); fs.writeFileSync( path.join(pluginHooksDir, 'hooks.json'), JSON.stringify(buildClaudePluginHooksManifest(), null, 2) + '\n', ); - console.log('📦 Built Claude Code plugin subtree at ./plugin/'); + console.log('📦 Built Claude Code / Grok Build plugin subtree at ./plugin/'); } else { console.log('📋 Skipped root harness and plugin sync (--skip-root-sync)'); } diff --git a/scripts/lib/transformers/hooks.js b/scripts/lib/transformers/hooks.js index 202085e12..513ee8091 100644 --- a/scripts/lib/transformers/hooks.js +++ b/scripts/lib/transformers/hooks.js @@ -7,9 +7,12 @@ * - Claude Code: `.claude/settings.json` (${CLAUDE_PROJECT_DIR}-relative) * - Codex: `.codex/hooks.json` * - Cursor: `.cursor/hooks.json` + * - Grok Build: `.grok/hooks/impeccable.json` * * 2. Claude Code plugin package (the marketplace / `/plugin install` path): * - `plugin/hooks/hooks.json` (${CLAUDE_PLUGIN_ROOT}-relative) + * Also consumed by Grok Build via Claude Code plugin compatibility + * (`CLAUDE_PLUGIN_ROOT` is aliased to `GROK_PLUGIN_ROOT`). * * 3. OpenAI plugin package: * - `hooks/hooks.json` (${PLUGIN_ROOT}-relative) @@ -49,6 +52,9 @@ const CODEX_PLUGIN_HOOK = '${PLUGIN_ROOT}/skills/impeccable/scripts/hook.mjs'; const CODEX_PROJECT_HOOK = '.agents/skills/impeccable/scripts/hook.mjs'; const CURSOR_BEFORE_EDIT_SCRIPT = '.cursor/skills/impeccable/scripts/hook-before-edit.mjs'; const GITHUB_PROJECT_HOOK = '$(git rev-parse --show-toplevel)/.github/skills/impeccable/scripts/hook.mjs'; +// Grok project hooks are relative to the git/workspace root. Claude tool names +// in the matcher (Edit|Write|MultiEdit) alias to Grok's search_replace family. +const GROK_PROJECT_HOOK = '.grok/skills/impeccable/scripts/hook.mjs'; export function buildClaudeSettingsManifest() { return { @@ -185,6 +191,32 @@ export function buildGitHubHooksManifest() { }; } +// Grok Build discovers project hooks from `.grok/hooks/*.json` and requires +// folder trust (`/hooks-trust` or `--trust`) before they run. Event schema is +// Claude-compatible (PostToolUse / Stop / PreToolUse); Claude tool names in +// matchers are aliased to Grok tools (Edit|Write|MultiEdit → search_replace). +// https://docs.x.ai/build/features/hooks +export function buildGrokHooksManifest() { + return { + hooks: { + PostToolUse: [ + { + matcher: 'Edit|Write|MultiEdit', + hooks: [ + { + type: 'command', + command: `node "${GROK_PROJECT_HOOK}"`, + timeout: TIMEOUT_SECONDS, + statusMessage: STATUS_MESSAGE, + }, + ], + }, + ], + Stop: [stopEntry(`node "${GROK_PROJECT_HOOK}"`)], + }, + }; +} + export function hooksJsonFor(provider) { switch (provider) { case 'claude': @@ -195,6 +227,8 @@ export function hooksJsonFor(provider) { return buildCursorHooksManifest(); case 'github': return buildGitHubHooksManifest(); + case 'grok': + return buildGrokHooksManifest(); default: return null; } diff --git a/scripts/lib/transformers/index.js b/scripts/lib/transformers/index.js index fbbb2fc6c..118a53d6c 100644 --- a/scripts/lib/transformers/index.js +++ b/scripts/lib/transformers/index.js @@ -16,5 +16,6 @@ export const transformPi = createTransformer(PROVIDERS.pi); export const transformQoder = createTransformer(PROVIDERS.qoder); export const transformRovoDev = createTransformer(PROVIDERS['rovo-dev']); export const transformVibe = createTransformer(PROVIDERS.vibe); +export const transformGrok = createTransformer(PROVIDERS.grok); export { createTransformer, PROVIDERS }; diff --git a/scripts/lib/transformers/providers.js b/scripts/lib/transformers/providers.js index 0ace62194..ed94cb704 100644 --- a/scripts/lib/transformers/providers.js +++ b/scripts/lib/transformers/providers.js @@ -129,4 +129,21 @@ export const PROVIDERS = { displayName: 'Mistral Vibe', frontmatterFields: ['user-invocable', 'license', 'compatibility', 'metadata', 'allowed-tools'], }, + grok: { + provider: 'grok', + providerTags: ['grok'], + configDir: '.grok', + displayName: 'Grok Build', + // Grok's skill frontmatter matches the Agent Skills spec plus Claude-style + // extensions (user-invocable, argument-hint, allowed-tools, model, effort). + // See https://docs.x.ai/build/features/skills-plugins-marketplaces and + // ~/.grok/docs/user-guide/08-skills.md. + frontmatterFields: ['user-invocable', 'argument-hint', 'license', 'compatibility', 'metadata', 'allowed-tools'], + // Project/user agents are markdown with YAML frontmatter (Claude-compatible). + agentFormat: 'claude-md', + emitHooks: 'grok', + // Grok discovers project hooks from `.grok/hooks/*.json` (not a single + // settings.json). Claude tool-name matchers alias to Grok tools. + hooksManifestRel: 'hooks/impeccable.json', + }, }; diff --git a/scripts/lib/utils.js b/scripts/lib/utils.js index f191350ee..157dbdc6b 100644 --- a/scripts/lib/utils.js +++ b/scripts/lib/utils.js @@ -633,6 +633,12 @@ export const PROVIDER_PLACEHOLDERS = { config_file: 'AGENTS.md', ask_instruction: 'ask the user directly to clarify what you cannot infer.', command_prefix: '/' + }, + 'grok': { + model: 'Grok', + config_file: 'AGENTS.md', + ask_instruction: 'STOP and call the AskUserQuestion tool to clarify.', + command_prefix: '/' } }; @@ -644,6 +650,7 @@ export const PROVIDER_BLOCK_TAGS = new Set([ 'cursor', 'gemini', 'github', + 'grok', 'kiro', 'opencode', 'pi', diff --git a/skill/scripts/context.mjs b/skill/scripts/context.mjs index 9892b4b84..9baaed5b9 100644 --- a/skill/scripts/context.mjs +++ b/skill/scripts/context.mjs @@ -1196,6 +1196,7 @@ const HOOK_MANIFESTS_BY_PROVIDER = Object.freeze({ agents: ['.codex/hooks.json'], cursor: ['.cursor/hooks.json'], github: ['.github/hooks/impeccable.json'], + grok: ['.grok/hooks/impeccable.json'], }); function truthyEnv(value) { @@ -1224,7 +1225,7 @@ function hookEnabledAt(root) { return enabled; } -const STOP_REVIEW_PROVIDERS = new Set(['claude-code', 'codex', 'agents']); +const STOP_REVIEW_PROVIDERS = new Set(['claude-code', 'codex', 'agents', 'grok']); function automaticHookMode(ctx) { if (ctx.platform === 'ios' || ctx.platform === 'android' || ctx.platform === 'adaptive') { diff --git a/skill/scripts/pin.mjs b/skill/scripts/pin.mjs index 52ea2701b..a3d1a1244 100644 --- a/skill/scripts/pin.mjs +++ b/skill/scripts/pin.mjs @@ -21,8 +21,8 @@ const __dirname = dirname(fileURLToPath(import.meta.url)); // All known harness directories const HARNESS_DIRS = [ - '.claude', '.cursor', '.gemini', '.codex', '.agents', - '.trae', '.trae-cn', '.pi', '.opencode', '.kiro', '.rovodev', + '.claude', '.cursor', '.gemini', '.codex', '.agents', '.github', '.grok', + '.trae', '.trae-cn', '.pi', '.opencode', '.kiro', '.rovodev', '.vibe', '.qoder', ]; const CODEX_HARNESSES = new Set(['.codex', '.agents']); diff --git a/tests/hook-build.test.mjs b/tests/hook-build.test.mjs index d18d5abbf..316ec5c61 100644 --- a/tests/hook-build.test.mjs +++ b/tests/hook-build.test.mjs @@ -15,6 +15,7 @@ import { buildCodexHooksManifest, buildCursorHooksManifest, buildGitHubHooksManifest, + buildGrokHooksManifest, hooksJsonFor, } from '../scripts/lib/transformers/hooks.js'; @@ -109,11 +110,33 @@ describe('hook manifest builders', () => { assert.equal(manifest.hooks.preToolUse, undefined); }); + it('builds Grok Build project hooks for the real detector hook', () => { + const manifest = buildGrokHooksManifest(); + const group = manifest.hooks.PostToolUse[0]; + const handler = group.hooks[0]; + + // Claude-compatible schema; Claude tool names alias to Grok tools at runtime. + assert.equal(group.matcher, 'Edit|Write|MultiEdit'); + assert.equal(handler.type, 'command'); + assert.equal(handler.timeout, 5); + assert.equal(handler.statusMessage, 'Checking UI changes'); + expectCommand(handler.command, '.grok/skills/impeccable/scripts/hook.mjs'); + assert.ok(!handler.command.includes('${CLAUDE_PROJECT_DIR}')); + assert.ok(!handler.command.includes('${GROK_PLUGIN_ROOT}')); + assert.equal(manifest.hooks.SessionStart, undefined); + + const stop = manifest.hooks.Stop[0].hooks[0]; + assert.equal(stop.timeout, 30); + assert.equal(stop.statusMessage, 'Design deep pass'); + expectCommand(stop.command, '.grok/skills/impeccable/scripts/hook.mjs'); + }); + it('routes supported hook builders and leaves other providers alone', () => { assert.ok(hooksJsonFor('claude')); assert.ok(hooksJsonFor('codex')); assert.ok(hooksJsonFor('cursor')); assert.ok(hooksJsonFor('github')); + assert.ok(hooksJsonFor('grok')); assert.equal(hooksJsonFor('gemini'), null); }); }); diff --git a/tests/skills-cli.test.js b/tests/skills-cli.test.js index 236a0a448..bb16beeb4 100644 --- a/tests/skills-cli.test.js +++ b/tests/skills-cli.test.js @@ -434,6 +434,24 @@ describe('skills link: submodule installs', () => { rmSync(tmp, { recursive: true, force: true }); }, 15000); + test('maps grok and grok-build provider aliases to .grok', () => { + const tmp = mkdtempSync(join(tmpdir(), 'imp-test-link-grok-')); + execSync('git init', { cwd: tmp }); + createFakeLinkSource(tmp, ['.grok']); + + run('skills link --source=.impeccable --providers=grok -y', { cwd: tmp }); + expect(lstatSync(join(tmp, '.grok', 'skills', 'impeccable')).isSymbolicLink()).toBe(true); + rmSync(tmp, { recursive: true, force: true }); + + const tmp2 = mkdtempSync(join(tmpdir(), 'imp-test-link-grok-build-')); + execSync('git init', { cwd: tmp2 }); + createFakeLinkSource(tmp2, ['.grok']); + + run('skills link --source=.impeccable --providers=grok-build -y', { cwd: tmp2 }); + expect(lstatSync(join(tmp2, '.grok', 'skills', 'impeccable')).isSymbolicLink()).toBe(true); + rmSync(tmp2, { recursive: true, force: true }); + }, 15000); + test('skills update leaves linked installs on the submodule path', () => { const tmp = mkdtempSync(join(tmpdir(), 'imp-test-link-update-')); execSync('git init', { cwd: tmp });