feat: add Gemini CLI support (#25)
* feat: add Gemini CLI support Add a thin Gemini CLI extension adapter (closes #22). The manifest points contextFileName at the existing AGENTS.md for always-on rules and reuses the repo's commands/*.toml (/ponytail, /ponytail-review) and skills/, which Gemini CLI auto-discovers — no rule text is duplicated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test: read manifest inside cases for a clean RED The manifest was parsed at module scope, so reverting the adapter crashed the whole test file at load (ENOENT) and collapsed all four cases into one unreadable failure. Read it inside each case via a helper that asserts existence first, so a missing or malformed manifest surfaces as clear per-case assertion failures instead of a stack trace. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
147bcfd621
commit
e01aa900f7
@@ -11,7 +11,7 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://img.shields.io/github/stars/DietrichGebert/ponytail?style=flat-square&color=111111&label=stars" alt="Stars">
|
<img src="https://img.shields.io/github/stars/DietrichGebert/ponytail?style=flat-square&color=111111&label=stars" alt="Stars">
|
||||||
<img src="https://img.shields.io/github/v/release/DietrichGebert/ponytail?style=flat-square&color=111111&label=release" alt="Release">
|
<img src="https://img.shields.io/github/v/release/DietrichGebert/ponytail?style=flat-square&color=111111&label=release" alt="Release">
|
||||||
<img src="https://img.shields.io/badge/works%20with-10%20agents-111111?style=flat-square" alt="Works with 10 agents">
|
<img src="https://img.shields.io/badge/works%20with-11%20agents-111111?style=flat-square" alt="Works with 11 agents">
|
||||||
<img src="https://img.shields.io/badge/license-MIT-111111?style=flat-square" alt="MIT license">
|
<img src="https://img.shields.io/badge/license-MIT-111111?style=flat-square" alt="MIT license">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -101,6 +101,14 @@ Run OpenCode from a checkout of this repo (the plugin reuses its `hooks/` and `s
|
|||||||
|
|
||||||
Injects the ruleset every turn at the active level; adds `/ponytail`, `/ponytail-review`, and `/ponytail-audit`. OpenCode also auto-loads this repo's `AGENTS.md`, so the rules hold even without the plugin. The plugin adds the `lite/full/ultra/off` levels.
|
Injects the ruleset every turn at the active level; adds `/ponytail`, `/ponytail-review`, and `/ponytail-audit`. OpenCode also auto-loads this repo's `AGENTS.md`, so the rules hold even without the plugin. The plugin adds the `lite/full/ultra/off` levels.
|
||||||
|
|
||||||
|
### Gemini CLI
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gemini extensions install https://github.com/DietrichGebert/ponytail
|
||||||
|
```
|
||||||
|
|
||||||
|
Loads the ruleset as always-on context every session and registers `/ponytail` and `/ponytail-review`; the `skills/` ship too, activated when a task needs them.
|
||||||
|
|
||||||
That was it. He'd be proud. He won't say it.
|
That was it. He'd be proud. He won't say it.
|
||||||
|
|
||||||
Active every session. `/ponytail-review` finds what to delete in your diff, `/ponytail-audit` does the same for the whole repo. `/ponytail ultra` exists for when the codebase has wronged you personally. `/ponytail-help` explains the rest.
|
Active every session. `/ponytail-review` finds what to delete in your diff, `/ponytail-audit` does the same for the whole repo. `/ponytail ultra` exists for when the codebase has wronged you personally. `/ponytail-help` explains the rest.
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ to load in a given agent.
|
|||||||
| Claude Code | `.claude-plugin/`, `commands/`, `hooks/` | Full plugin install with session activation, mode tracking, commands, and statusline support. |
|
| Claude Code | `.claude-plugin/`, `commands/`, `hooks/` | Full plugin install with session activation, mode tracking, commands, and statusline support. |
|
||||||
| Codex | `.codex-plugin/plugin.json`, `hooks/hooks.json`, `hooks/`, `skills/` | Plugin install with the same skills plus lifecycle hooks for activation and mode tracking. |
|
| Codex | `.codex-plugin/plugin.json`, `hooks/hooks.json`, `hooks/`, `skills/` | Plugin install with the same skills plus lifecycle hooks for activation and mode tracking. |
|
||||||
| OpenCode | `.opencode/plugins/ponytail.mjs`, `.opencode/command/`, `hooks/`, `skills/` | Server plugin injects the ruleset each turn via `experimental.chat.system.transform` and persists `/ponytail` switches; reuses the shared instruction builder. |
|
| OpenCode | `.opencode/plugins/ponytail.mjs`, `.opencode/command/`, `hooks/`, `skills/` | Server plugin injects the ruleset each turn via `experimental.chat.system.transform` and persists `/ponytail` switches; reuses the shared instruction builder. |
|
||||||
|
| Gemini CLI | `gemini-extension.json`, `AGENTS.md`, `commands/`, `skills/` | Extension manifest points `contextFileName` at `AGENTS.md` for always-on rules, and reuses the existing `commands/*.toml` (`/ponytail`, `/ponytail-review`) and `skills/`, which Gemini CLI auto-discovers. |
|
||||||
| Cursor | `.cursor/rules/ponytail.mdc` | Always-on project rule. |
|
| Cursor | `.cursor/rules/ponytail.mdc` | Always-on project rule. |
|
||||||
| Windsurf | `.windsurf/rules/ponytail.md` | Project rule. |
|
| Windsurf | `.windsurf/rules/ponytail.md` | Project rule. |
|
||||||
| Cline | `.clinerules/ponytail.md` | Project rule. |
|
| Cline | `.clinerules/ponytail.md` | Project rule. |
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "ponytail",
|
||||||
|
"version": "4.2.0",
|
||||||
|
"description": "Lazy senior dev mode. Forces the simplest, shortest solution that actually works: YAGNI, stdlib first, no unrequested abstractions.",
|
||||||
|
"contextFileName": "AGENTS.md"
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
// Smoke test for the Gemini CLI adapter. The adapter is a single thin manifest
|
||||||
|
// (gemini-extension.json) that reuses the repo's existing files: AGENTS.md for
|
||||||
|
// always-on context, commands/*.toml for /ponytail + /ponytail-review, and
|
||||||
|
// skills/ for the agent skills. This test fails if the manifest is removed,
|
||||||
|
// loses its pinned version, or points contextFileName at a file that no longer
|
||||||
|
// carries the load-bearing rules — i.e. if the adapter stops wiring ponytail.
|
||||||
|
|
||||||
|
const test = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const root = path.join(__dirname, '..');
|
||||||
|
const MANIFEST = 'gemini-extension.json';
|
||||||
|
const EXTENSION_NAME = 'ponytail';
|
||||||
|
// Floating refs are a supply-chain footgun; the manifest version must be pinned.
|
||||||
|
const PINNED_SEMVER = /^\d+\.\d+\.\d+$/;
|
||||||
|
// Gemini auto-discovers these by directory; the manifest is only useful if they exist.
|
||||||
|
const REUSED_COMMANDS = ['commands/ponytail.toml', 'commands/ponytail-review.toml'];
|
||||||
|
const REUSED_SKILLS = ['skills/ponytail/SKILL.md'];
|
||||||
|
// Same load-bearing phrases asserted by scripts/check-rule-copies.js: the file
|
||||||
|
// contextFileName points at must actually carry the rules, not just exist.
|
||||||
|
const RULE_INVARIANTS = [
|
||||||
|
'lazy senior',
|
||||||
|
'input validation at trust boundaries',
|
||||||
|
'naive heuristic',
|
||||||
|
];
|
||||||
|
|
||||||
|
function read(relPath) {
|
||||||
|
return fs.readFileSync(path.join(root, relPath), 'utf8');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read inside each test (not at module scope) so a missing or malformed manifest
|
||||||
|
// surfaces as a clean per-test assertion failure, not a load-time crash that
|
||||||
|
// collapses every case into one unreadable stack trace.
|
||||||
|
function loadManifest() {
|
||||||
|
assert.ok(fs.existsSync(path.join(root, MANIFEST)), `${MANIFEST} must exist`);
|
||||||
|
return JSON.parse(read(MANIFEST));
|
||||||
|
}
|
||||||
|
|
||||||
|
test('manifest names the ponytail extension with a pinned version', () => {
|
||||||
|
const manifest = loadManifest();
|
||||||
|
assert.equal(manifest.name, EXTENSION_NAME);
|
||||||
|
assert.match(manifest.version, PINNED_SEMVER);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('version stays aligned with the other plugin manifests', () => {
|
||||||
|
const manifest = loadManifest();
|
||||||
|
const claude = JSON.parse(read('.claude-plugin/plugin.json'));
|
||||||
|
assert.equal(manifest.version, claude.version);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('contextFileName resolves to a file carrying the ponytail rules', () => {
|
||||||
|
const manifest = loadManifest();
|
||||||
|
assert.ok(manifest.contextFileName, 'contextFileName must be set so rules load every session');
|
||||||
|
const context = read(manifest.contextFileName);
|
||||||
|
for (const phrase of RULE_INVARIANTS) {
|
||||||
|
assert.ok(context.includes(phrase), `context file missing rule invariant: "${phrase}"`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the commands and skills the adapter reuses are present', () => {
|
||||||
|
for (const rel of [...REUSED_COMMANDS, ...REUSED_SKILLS]) {
|
||||||
|
assert.ok(fs.existsSync(path.join(root, rel)), `reused file missing: ${rel}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user