mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-12 14:16:28 +03:00
Merge pull request #461 from pawelad/pawelad/add-antigravity-support
Add Google Antigravity provider support
This commit is contained in:
@@ -270,6 +270,16 @@ 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.
|
||||
|
||||
**Google Antigravity:**
|
||||
```bash
|
||||
# Project-specific
|
||||
cp -r dist/antigravity/.agent your-project/
|
||||
|
||||
# Or global (applies to all projects)
|
||||
mkdir -p ~/.gemini/config/skills
|
||||
cp -r dist/antigravity/.agent/skills/* ~/.gemini/config/skills/
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Once installed, every command runs through the single `/impeccable` skill:
|
||||
@@ -400,6 +410,7 @@ Full detector docs: [impeccable.style/docs/detector](https://impeccable.style/do
|
||||
- [Rovo Dev](https://www.atlassian.com/software/rovo)
|
||||
- [Qoder](https://qoder.com)
|
||||
- [Mistral Vibe](https://docs.mistral.ai/vibe/code/overview)
|
||||
- [Google Antigravity](https://antigravity.google)
|
||||
|
||||
## Community & Ecosystem
|
||||
|
||||
|
||||
@@ -23,9 +23,11 @@ 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', '.grok', '.kiro', '.opencode', '.pi', '.qoder', '.trae', '.trae-cn', '.rovodev', '.vibe'];
|
||||
const PROVIDER_DIRS = ['.claude', '.cursor', '.gemini', '.agents', '.agent', '.github', '.grok', '.kiro', '.opencode', '.pi', '.qoder', '.trae', '.trae-cn', '.rovodev', '.vibe'];
|
||||
const PROVIDER_ALIASES = {
|
||||
agent: '.agent',
|
||||
agents: '.agents',
|
||||
antigravity: '.agent',
|
||||
claude: '.claude',
|
||||
'claude-code': '.claude',
|
||||
codex: '.agents',
|
||||
@@ -48,6 +50,7 @@ const PROVIDER_ALIASES = {
|
||||
};
|
||||
|
||||
const PROVIDER_DISPLAY = {
|
||||
'.agent': { name: 'Antigravity', input: 'antigravity' },
|
||||
'.agents': { name: 'Codex CLI', input: 'codex' },
|
||||
'.claude': { name: 'Claude Code', input: 'claude' },
|
||||
'.cursor': { name: 'Cursor', input: 'cursor' },
|
||||
@@ -63,7 +66,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', 'grok', 'kiro', 'opencode', 'pi', 'qoder', 'trae', 'trae-cn', 'rovo-dev', 'vibe'];
|
||||
const PROVIDER_INPUT_ORDER = ['antigravity', 'claude', 'codex', 'cursor', 'gemini', 'github', 'grok', 'kiro', 'opencode', 'pi', 'qoder', 'trae', 'trae-cn', 'rovo-dev', 'vibe'];
|
||||
|
||||
// OpenCode reads global skills from its config directory, not ~/.opencode:
|
||||
// $OPENCODE_CONFIG_DIR, else $XDG_CONFIG_HOME/opencode, else
|
||||
@@ -78,8 +81,11 @@ function opencodeGlobalConfigDir(home) {
|
||||
// Providers whose GLOBAL (home) skills dir is not `<provider>/skills`,
|
||||
// as a function of the home dir. Pi discovers global skills from
|
||||
// ~/.pi/agent/skills/ (issue #327); OpenCode from its config dir (issue
|
||||
// #406). Project scope stays `<provider>/skills` for both.
|
||||
// #406). Antigravity's global skills dir is ~/.gemini/config/skills/
|
||||
// (shared Gemini config location); project scope stays `.agent/skills`.
|
||||
// Project scope stays `<provider>/skills` for all of these.
|
||||
const HOME_SKILLS_DIR_OVERRIDES = {
|
||||
'.agent': (home) => join(home, '.gemini', 'config', 'skills'),
|
||||
'.pi': (home) => join(home, '.pi', 'agent', 'skills'),
|
||||
'.opencode': (home) => join(opencodeGlobalConfigDir(home), 'skills'),
|
||||
};
|
||||
@@ -88,6 +94,12 @@ const HOME_SKILLS_DIR_OVERRIDES = {
|
||||
// installed harnesses (~/.claude, ~/.codex, ...). Codex reads skills from
|
||||
// .agents/skills, so ~/.codex maps to the .agents bundle variant.
|
||||
const GLOBAL_HARNESS_HINTS = [
|
||||
{ home: '.agent', provider: '.agent' },
|
||||
// Antigravity nests under ~/.gemini/ too, so any of these also trips the
|
||||
// .gemini hint above (harmless double-detection — both get pre-selected).
|
||||
{ home: '.gemini/antigravity', provider: '.agent' },
|
||||
{ home: '.gemini/antigravity-cli', provider: '.agent' },
|
||||
{ home: '.gemini/antigravity-ide', provider: '.agent' },
|
||||
{ home: '.claude', provider: '.claude' },
|
||||
{ home: '.codex', provider: '.agents' },
|
||||
{ home: '.cursor', provider: '.cursor' },
|
||||
@@ -598,7 +610,7 @@ async function copyOrExtractLocalBundle(sourceValue) {
|
||||
*/
|
||||
function normalizeForHash(content) {
|
||||
return content
|
||||
.replace(/\.(claude|cursor|agents|github|gemini|codex|grok|kiro|opencode|pi|qoder|trae|trae-cn|rovodev|vibe)\/skills\//g, '.PROVIDER/skills/');
|
||||
.replace(/\.(claude|cursor|agents|agent|github|gemini|codex|grok|kiro|opencode|pi|qoder|trae|trae-cn|rovodev|vibe)\/skills\//g, '.PROVIDER/skills/');
|
||||
}
|
||||
|
||||
function hashSkillFile(filePath) {
|
||||
|
||||
@@ -4,6 +4,7 @@ export const FILE_DOWNLOAD_PROVIDER_CONFIG_DIRS = Object.freeze({
|
||||
gemini: '.gemini',
|
||||
codex: '.codex',
|
||||
agents: '.agents',
|
||||
antigravity: '.agent',
|
||||
github: '.github',
|
||||
grok: '.grok',
|
||||
kiro: '.kiro',
|
||||
|
||||
+19
-16
@@ -26,6 +26,7 @@ Last verified: 2026-04-28 (subagent landscape spot-checked 2026-06-28; Mistral V
|
||||
| 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 |
|
||||
| Antigravity | https://antigravity.google/docs/skills |
|
||||
|
||||
## Spec Compliance
|
||||
|
||||
@@ -37,22 +38,22 @@ 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 | 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 |
|
||||
| Field | Claude Code | Cursor | Gemini | Codex | Copilot | Grok | Kiro | OpenCode | Pi | Qoder | Rovo Dev | Mistral Vibe | Antigravity |
|
||||
|-------|:-----------:|:------:|:------:|:-----:|:-------:|:----:|:----:|:--------:|:--:|:-----:|:--------:|:------------:|:-----------:|
|
||||
| `name`* | Yes | 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 | Yes |
|
||||
| `license`* | Yes | Yes | Ignored | No | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
|
||||
| `compatibility`* | Yes | Yes | Ignored | No | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
|
||||
| `metadata`* | Yes | Yes | Ignored | No | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
|
||||
| `allowed-tools`* | Yes | No | Ignored | No | No | Yes | No | Yes | Yes | Yes | Yes | Yes | Yes |
|
||||
| `user-invocable` | Yes | No | No | No | Yes | Yes | No | Yes | No | Yes | Yes | Yes | No |
|
||||
| `argument-hint` | Yes | No | No | No | Yes | Yes | No | Yes | No | Yes | Yes | No | No |
|
||||
| `disable-model-invocation` | Yes | Yes | No | No | Yes | Yes | No | Yes | Yes | TBD | TBD | No | No |
|
||||
| `model` | Yes | No | No | No | No | Yes | No | Yes | No | No | No | No | No |
|
||||
| `effort` | Yes | No | No | No | No | Yes | No | No | No | No | No | No | No |
|
||||
| `context` | Yes | No | No | No | No | No | No | No | No | No | No | No | No |
|
||||
| `agent` | Yes | No | No | No | No | No | No | Yes | No | No | No | No | No |
|
||||
| `hooks` | Yes | No | No | Yes | No | Yes | No | No | No | No | No | No | No |
|
||||
|
||||
Notes:
|
||||
- Gemini CLI validates only `name` and `description`; other spec fields are parsed but ignored.
|
||||
@@ -60,6 +61,7 @@ Notes:
|
||||
- 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.
|
||||
- Antigravity supports standard Agent Skills spec frontmatter fields (`name`, `description`, `license`, `compatibility`, `metadata`, `allowed-tools`).
|
||||
- Unknown fields are silently ignored by all harnesses.
|
||||
|
||||
## Hook surface used by Impeccable
|
||||
@@ -90,6 +92,7 @@ Notes:
|
||||
| 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) |
|
||||
| Antigravity | `.agent/skills/` (project), `~/.gemini/config/skills/` (global) | `.agents/skills/` (project), `~/.agents/skills/` (global) |
|
||||
|
||||
All harnesses support the `{skill-name}/SKILL.md` directory structure with optional `reference/`, `scripts/`, and `assets/` subdirectories.
|
||||
|
||||
|
||||
@@ -466,6 +466,7 @@ This folder contains skills for all supported tools:
|
||||
.gemini/ -> Gemini CLI
|
||||
.codex/ -> Codex custom agents (Codex skills use .agents/)
|
||||
.agents/ -> Codex CLI
|
||||
.agent/ -> Antigravity
|
||||
.github/ -> GitHub Copilot
|
||||
.grok/ -> Grok Build
|
||||
.kiro/ -> Kiro
|
||||
|
||||
@@ -17,5 +17,6 @@ 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 const transformAntigravity = createTransformer(PROVIDERS.antigravity);
|
||||
|
||||
export { createTransformer, PROVIDERS };
|
||||
|
||||
@@ -155,4 +155,11 @@ export const PROVIDERS = {
|
||||
// settings.json). Claude tool-name matchers alias to Grok tools.
|
||||
hooksManifestRel: 'hooks/impeccable.json',
|
||||
},
|
||||
antigravity: {
|
||||
provider: 'antigravity',
|
||||
providerTags: ['antigravity'],
|
||||
configDir: '.agent',
|
||||
displayName: 'Antigravity',
|
||||
frontmatterFields: ['license', 'compatibility', 'metadata', 'allowed-tools'],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -639,11 +639,18 @@ export const PROVIDER_PLACEHOLDERS = {
|
||||
config_file: 'AGENTS.md',
|
||||
ask_instruction: 'STOP and call the AskUserQuestion tool to clarify.',
|
||||
command_prefix: '/'
|
||||
},
|
||||
'antigravity': {
|
||||
model: 'Gemini',
|
||||
config_file: 'AGENTS.md',
|
||||
ask_instruction: 'ask the user directly to clarify what you cannot infer.',
|
||||
command_prefix: '/'
|
||||
}
|
||||
};
|
||||
|
||||
export const PROVIDER_BLOCK_TAGS = new Set([
|
||||
'agents',
|
||||
'antigravity',
|
||||
'claude',
|
||||
'claude-code',
|
||||
'codex',
|
||||
|
||||
@@ -21,7 +21,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
// All known harness directories
|
||||
const HARNESS_DIRS = [
|
||||
'.claude', '.cursor', '.gemini', '.codex', '.agents', '.github', '.grok',
|
||||
'.claude', '.cursor', '.gemini', '.codex', '.agents', '.agent', '.github', '.grok',
|
||||
'.trae', '.trae-cn', '.pi', '.opencode', '.kiro', '.rovodev', '.vibe', '.qoder',
|
||||
];
|
||||
|
||||
|
||||
@@ -148,6 +148,7 @@ This is a test skill body.`;
|
||||
transformers.transformClaudeCode(skills, DIST_DIR, patterns);
|
||||
transformers.transformGemini(skills, DIST_DIR, patterns);
|
||||
transformers.transformCodex(skills, DIST_DIR, patterns);
|
||||
transformers.transformAntigravity(skills, DIST_DIR, patterns);
|
||||
|
||||
// Verify Cursor outputs
|
||||
expect(fs.existsSync(path.join(DIST_DIR, 'cursor/.cursor/skills/test-skill/SKILL.md'))).toBe(true);
|
||||
@@ -160,6 +161,9 @@ This is a test skill body.`;
|
||||
|
||||
// Verify Codex outputs
|
||||
expect(fs.existsSync(path.join(DIST_DIR, 'codex/.codex/skills/test-skill/SKILL.md'))).toBe(true);
|
||||
|
||||
// Verify Antigravity outputs
|
||||
expect(fs.existsSync(path.join(DIST_DIR, 'antigravity/.agent/skills/test-skill/SKILL.md'))).toBe(true);
|
||||
});
|
||||
|
||||
test('integration: emits native subagent files for Codex, Claude Code, GitHub Copilot, and Cursor', () => {
|
||||
@@ -356,6 +360,18 @@ Please audit {{target}} for technical quality. Ask {{model}} for help.`;
|
||||
expect(fs.existsSync(path.join(DIST_DIR, 'github/.github/skills'))).toBe(true);
|
||||
expect(fs.existsSync(path.join(DIST_DIR, 'kiro/.kiro/skills'))).toBe(true);
|
||||
});
|
||||
|
||||
test('Antigravity transformer emits skills under .agent/', () => {
|
||||
const { skills } = utils.readSourceFiles(TEST_DIR);
|
||||
const patterns = utils.readPatterns(TEST_DIR);
|
||||
const DIST_DIR = path.join(TEST_DIR, 'dist');
|
||||
|
||||
// Should not throw
|
||||
transformers.transformAntigravity(skills, DIST_DIR, patterns);
|
||||
|
||||
// Verify the harness directory is created at the correct path
|
||||
expect(fs.existsSync(path.join(DIST_DIR, 'antigravity/.agent/skills'))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
// Resolve a relative import specifier against the importer's bundle-relative
|
||||
|
||||
@@ -788,6 +788,47 @@ describe('skills install/update: local universal bundle e2e', () => {
|
||||
rmSync(tmp, { recursive: true, force: true });
|
||||
}, 15000);
|
||||
|
||||
test('installs Antigravity skills into .agent/ with --providers=antigravity', () => {
|
||||
const tmp = mkdtempSync(join(tmpdir(), 'imp-test-antigravity-install-'));
|
||||
execSync('git init', { cwd: tmp });
|
||||
const bundleRoot = createFakeUniversalBundle(tmp, ['.agent']);
|
||||
|
||||
const output = run('skills install -y --providers=antigravity --no-hooks', {
|
||||
cwd: tmp,
|
||||
env: { ...process.env, IMPECCABLE_BUNDLE_PATH: bundleRoot },
|
||||
});
|
||||
expect(output).toContain('Done!');
|
||||
|
||||
const skillDir = join(tmp, '.agent', 'skills', 'impeccable');
|
||||
expect(existsSync(join(skillDir, 'SKILL.md'))).toBe(true);
|
||||
expect(readFileSync(join(skillDir, 'SKILL.md'), 'utf8')).toContain('Local deterministic bundle for .agent.');
|
||||
expect(existsSync(join(skillDir, 'scripts', 'context.mjs'))).toBe(true);
|
||||
|
||||
rmSync(tmp, { recursive: true, force: true });
|
||||
}, 15000);
|
||||
|
||||
test('updates stale Antigravity skills at .agent/ from the local bundle', () => {
|
||||
const tmp = mkdtempSync(join(tmpdir(), 'imp-test-antigravity-update-'));
|
||||
execSync('git init', { cwd: tmp });
|
||||
const bundleRoot = createFakeUniversalBundle(tmp, ['.agent']);
|
||||
|
||||
const skillDir = join(tmp, '.agent', 'skills', 'impeccable');
|
||||
mkdirSync(skillDir, { recursive: true });
|
||||
writeFileSync(join(skillDir, 'SKILL.md'), '---\nname: impeccable\nstale: true\n---\nOld content.\n');
|
||||
|
||||
const output = run('skills update -y --no-hooks', {
|
||||
cwd: tmp,
|
||||
env: { ...process.env, IMPECCABLE_BUNDLE_PATH: bundleRoot },
|
||||
});
|
||||
expect(output).toContain('Updated');
|
||||
|
||||
const content = readFileSync(join(skillDir, 'SKILL.md'), 'utf8');
|
||||
expect(content).not.toContain('stale: true');
|
||||
expect(content).toContain('version: 9.9.9-local');
|
||||
|
||||
rmSync(tmp, { recursive: true, force: true });
|
||||
}, 15000);
|
||||
|
||||
test('interactive install explains home detections and can target the project root', () => {
|
||||
const tmp = mkdtempSync(join(tmpdir(), 'imp-test-interactive-project-'));
|
||||
const home = mkdtempSync(join(tmpdir(), 'imp-home-interactive-project-'));
|
||||
|
||||
Reference in New Issue
Block a user