Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
"planning",
"ai assisted engineering",
"sdlc",
"software development lifecyle"
"software development lifecyle",
"claude code",
"github copilot cli"
]
}
33 changes: 33 additions & 0 deletions .github/plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "vgv-wingspan",
"description": "VGV Wingspan - AI-native workflows following Very Good Ventures best practices.",
"version": "0.0.4",
"author": {
"name": "Very Good Ventures",
"url": "https://verygood.ventures"
},
"homepage": "https://github.com/VeryGoodOpenSource/vgv-wingspan",
"repository": "https://github.com/VeryGoodOpenSource/vgv-wingspan",
"keywords": [
"software engineering",
"workflow",
"best practices",
"flow automation",
"code generation",
"planning",
"ai assisted engineering",
"sdlc",
"software development lifecyle",
"claude code",
"github copilot cli"
],
"skills": "skills",
"agents": [
"agents/analysis",
"agents/codebase-review",
"agents/quality-review",
"agents/research"
],
"hooks": "hooks/hooks.json",
"mcpServers": ".mcp.json"
}
63 changes: 63 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,66 @@ jobs:
run: npm install -g @anthropic-ai/claude-code
- name: Validate Plugin
run: claude plugin validate .
copilot-manifest:
name: 🐙 Copilot Manifest
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Validate Copilot manifest and metadata parity
run: |
set -euo pipefail
GH=.github/plugin/plugin.json
CC=.claude-plugin/plugin.json

# 1. Both manifests are valid JSON.
jq empty "$GH"
jq empty "$CC"

# 2. name is present and kebab-case (Copilot's only hard manifest rule).
NAME=$(jq -r '.name' "$GH")
echo "$NAME" | grep -qE '^[a-z0-9]+(-[a-z0-9]+)*$' \
|| { echo "::error::.github/plugin/plugin.json name '$NAME' is not kebab-case"; exit 1; }
[ "${#NAME}" -le 64 ] || { echo "::error::plugin name exceeds 64 chars"; exit 1; }

# 3. Shared metadata is identical across both manifests (only the
# Copilot manifest's component-path fields may differ).
if ! diff <(jq -S 'del(.skills,.agents,.hooks,.mcpServers,.commands,.extensions,.lspServers)' "$GH") \
<(jq -S . "$CC"); then
echo "::error::Metadata drift between .github/plugin/plugin.json and .claude-plugin/plugin.json"
exit 1
fi

# 4. Every component path the Copilot manifest declares exists on disk.
for key in skills hooks mcpServers; do
p=$(jq -r --arg k "$key" '.[$k]' "$GH")
[ -e "$p" ] || { echo "::error::$key path '$p' does not exist"; exit 1; }
done
jq -r '.agents[]' "$GH" | while read -r d; do
[ -d "$d" ] || { echo "::error::agents path '$d' does not exist"; exit 1; }
done
echo "Copilot manifest OK: metadata in parity, all component paths present."
copilot-loader:
name: 🐙 Copilot Loader Smoke Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Install GitHub Copilot CLI
# Plugin-management subcommands need no auth or Copilot seat, so this
# smoke test runs on any PR (including forks) with no secrets. Pin the
# version because direct local-path installs are on a deprecation path.
run: npm install -g @github/copilot@1.0.70
- name: Install plugin and assert every skill loads
run: |
set -euo pipefail
copilot --version
copilot plugin install ./
copilot skill list --json > skills.json
jq -r '.[] | select(.source=="plugin") | .name' skills.json | sort
# Copilot namespaces a skill to "vgv-wingspan:<name>" only if another
# installed plugin defines the same name; accept either form so the
# check is robust to unrelated plugins in the environment.
for skill in brainstorm plan build quality-review hotfix debrief create create-pr rebase refine-approach plan-technical-review elements-of-style; do
jq -e --arg s "$skill" 'any(.[]; .name == $s or .name == "vgv-wingspan:" + $s)' skills.json >/dev/null \
|| { echo "::error::Copilot did not load the '$skill' skill from the plugin"; exit 1; }
done
echo "All 12 Wingspan skills loaded on Copilot CLI."
5 changes: 5 additions & 0 deletions .release-please-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@
"path": ".claude-plugin/plugin.json",
"jsonpath": "$.version"
},
{
"type": "json",
"path": ".github/plugin/plugin.json",
"jsonpath": "$.version"
},
{
"type": "json",
"path": ".claude-plugin/marketplace.json",
Expand Down
16 changes: 10 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# VGV Wingspan

Wingspan is a collection of AI-assisted engineering tools — skills, agents, and hooks — released as a Claude Code plugin.
Wingspan is a collection of AI-assisted engineering tools — skills, agents, and hooks — released as a plugin for both Claude Code and GitHub Copilot CLI.

## Philosophy

Expand All @@ -22,7 +22,7 @@ The plugin supports three sequential phases:

Standalone Skills:

- **`/review`** — Run quality review agents on demand, independent of the build workflow.
- **`/quality-review`** — Run quality review agents on demand, independent of the build workflow. (Named `quality-review`, not `review`, to avoid GitHub Copilot CLI's built-in `/review` command.)

- **`/debrief`** — Produce a structured, blameless debrief document after an incident, failed release, or significant bug.

Expand Down Expand Up @@ -60,12 +60,16 @@ class), and renders one consolidated report plus a matching chat summary (see
- `docs/plan/` — Implementation plans from `/plan`
- `docs/reviews/` — Consolidated `review.md` + per-agent `raw/` from `/build` (ephemeral, cleaned up by build)
- `docs/hotfix-review/` — Consolidated `review.md` + per-agent `raw/` from `/hotfix` (ephemeral, cleaned up by hotfix)
- `docs/code-review/` — One `<slug>/` directory per run (`review.md` + per-agent `raw/`) from `/review` (standalone, user-managed)
- `docs/code-review/` — One `<slug>/` directory per run (`review.md` + per-agent `raw/`) from `/quality-review` (standalone, user-managed)
- `docs/debriefs/` — Debrief documents from `/debrief`

## Host Support

Wingspan is single-source, dual-host. `.claude-plugin/plugin.json` is the Claude Code manifest; `.github/plugin/plugin.json` is the GitHub Copilot CLI manifest (same metadata, plus explicit `skills`/`agents`/`hooks`/`mcpServers` component paths — Copilot does not discover agents in subdirectories without them). Release tooling bumps both versions. Copilot CLI reads Claude-format hooks, matchers, and `${CLAUDE_PLUGIN_ROOT}` natively, ignores unknown SKILL.md frontmatter, and accepts Claude agent frontmatter (`model: sonnet/haiku/inherit`, `effort`, `skills:`). Skill bodies stay Claude-first but carry short fallbacks where hosts differ: `$ARGUMENTS` substitution, `${CLAUDE_SKILL_DIR}` script paths, `ask_user` as the AskUserQuestion equivalent, and `vgv-wingspan:`-prefixed agent names. The one built-in slash collision on Copilot is `/review`, which is why the standalone review skill is named `quality-review` (its bare name is slash-reachable on both hosts); `/plan` is not a Copilot built-in and works as-is.

## Hooks

Wingspan uses Claude Code hooks to automate behavior at tool-call boundaries. Hooks are defined in `hooks/hooks.json`.
Wingspan uses hooks to automate behavior at tool-call boundaries. Hooks are defined in `hooks/hooks.json` (Claude Code format — GitHub Copilot CLI executes the same file natively).

### Companion Plugin Recommendations

Expand All @@ -74,7 +78,7 @@ A `PreToolUse` hook runs on every `Read`, `Glob`, or `Grep` call. It detects the
**How it works:**

1. `hooks/recommend-plugins.sh` fires on the first matched tool call and scans every JSON file in `hooks/recommendations/`. Each file declares a detection rule and the plugin to recommend.
2. Every file whose detection rule matches — and whose plugin isn't already installed — is collected. All matching recommendations are emitted together in a single `additionalContext` message.
2. Every file whose detection rule matches — and whose plugin isn't already installed — is collected. All matching recommendations are emitted together in a single `additionalContext` message. The script detects its host (Copilot CLI exports `COPILOT_CLI`/`COPILOT_PLUGIN_ROOT` to hook commands), checks that host's plugin settings (`.claude/` settings files on Claude Code; `~/.copilot` config and `.github/copilot/settings.json` on Copilot), and phrases the install commands for that host.
3. A marker file (`/tmp/wingspan-recommend-plugins-<hash>`) is written only when at least one recommendation is emitted, suppressing repeats for the rest of the session. If no plugins are missing, no marker is written and the script re-evaluates on the next tool call — so a newly added recommendation file can still fire later in the same session.

**Recommendation file format** (`hooks/recommendations/<plugin-name>.json`):
Expand All @@ -96,7 +100,7 @@ A `PreToolUse` hook runs on every `Read`, `Glob`, or `Grep` call. It detects the
| `detect.files` | Shell glob — greps inside every matching file for `pattern` |
| `detect.pattern` | Regex grep pattern to confirm the match |
| `verificationSkill` | Optional. Skill the `/build` and `/hotfix` ship gate delegates to when this file's `detect` matches and the skill is installed |
| `marketplace` | GitHub `owner/repo` for the marketplace registry |
| `marketplace` | GitHub `owner/repo` for the marketplace registry. Use the canonical repo path — on Copilot CLI the install spec is derived as `<plugin>@<repo-name>`, which must match the marketplace.json `name` |
| `description` | One-line summary shown in the recommendation |

**Adding a new recommendation:** Drop a JSON file in `hooks/recommendations/` following the format above. No code changes required. All matching files are evaluated.
Expand Down
37 changes: 36 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,17 @@ After the frontmatter, structure the file as:
2. **Core Standards** — enforced constraints, always first
3. **Content sections** — architecture, code examples, workflows, anti-patterns

**Handling arguments portably.** A skill receives its invocation arguments through the `$ARGUMENTS` token, which Claude Code substitutes wherever it appears in the body. GitHub Copilot CLI does **not** substitute it — the token stays literal. If a skill reads `$ARGUMENTS` (e.g. `<feature description>$ARGUMENTS</feature description>`), follow it with this exact fallback line so the skill still works on Copilot:

```markdown
If the text above still shows a literal placeholder instead of your input (e.g., on GitHub Copilot CLI, which does not substitute it), use whatever the user wrote after the skill name instead.
```

As with `${CLAUDE_SKILL_DIR}` (see [Shared scripts](#shared-scripts)), the fallback line must not quote `$ARGUMENTS` itself — Claude Code would substitute the mention and corrupt the sentence.

### 2. Update `plugin.json` tags

Add relevant keywords to the `keywords` array in `.claude-plugin/plugin.json`.
Add relevant keywords to the `keywords` array in **both** manifests: `.claude-plugin/plugin.json` (Claude Code) and `.github/plugin/plugin.json` (GitHub Copilot CLI). The two files carry the same metadata; only the Copilot manifest additionally declares component paths (`skills`, `agents`, `hooks`, `mcpServers`).

### 3. Update the README skills table

Expand Down Expand Up @@ -142,6 +150,14 @@ ${CLAUDE_SKILL_DIR}/scripts/detect-base-branch.sh
```
````

GitHub Copilot CLI does not substitute `${CLAUDE_SKILL_DIR}`, so whenever a skill body invokes a bundled script this way, follow the code block with this exact fallback line:

```markdown
If the path above appears unexpanded (e.g., on GitHub Copilot CLI, which does not substitute this variable), run the script from this skill's own `scripts/` directory instead.
```

The line must **not** name `${CLAUDE_SKILL_DIR}` (or `$ARGUMENTS`) itself: Claude Code substitutes those tokens everywhere they appear in the body — including inside an explanatory sentence — so a fallback that quotes the token gets corrupted on the very host where the token *does* work. Describe the symptom ("appears unexpanded", "shows a literal placeholder") instead of naming the token. Copilot's plugin installer dereferences the symlinks into real files, so the relative `scripts/` path always resolves.

Keep scripts executable (`chmod +x`) so they can be invoked directly without a `bash` wrapper. Avoid the fenced `` ```! `` auto-execute form — under stricter permission checks (Claude Code v2.1.98+) it passes the literal block content (including the `!` prefix) to the permission matcher, which no longer aligns with a `Bash(<path>)` pattern.

### When to use scripts vs inline bash
Expand Down Expand Up @@ -241,6 +257,25 @@ Then, inside a session:
/plugin install vgv-wingspan
```

### Test on GitHub Copilot CLI

Wingspan also runs as a [Copilot CLI plugin](https://docs.github.com/copilot/concepts/agents/copilot-cli/about-cli-plugins) via the `.github/plugin/plugin.json` manifest. To exercise your working copy there:

```bash
copilot plugin install ./ # from the repo root; reinstall after every edit (components are cached)
copilot plugin list # confirm vgv-wingspan appears
```

Then, in an interactive `copilot` session:

| Component | How to verify |
| --------- | ------------- |
| **Skills** | `/skills list` — Wingspan skills appear under their bare names (no `vgv-wingspan:` prefix). |
| **Agents** | `/agent` — plugin agents appear as `vgv-wingspan:<agent-name>`. |
| **Hooks** | Same as Claude Code: `Read` a file in a detectable project and watch for the recommendation. The same `/tmp/wingspan-recommend-plugins-*` marker applies. |

When finished: `copilot plugin uninstall vgv-wingspan`. Note that Copilot CLI reads Claude-format hooks and matchers natively, exports `${CLAUDE_PLUGIN_ROOT}` to hook commands, and ignores unknown SKILL.md frontmatter — so most changes need no Copilot-specific work. One naming rule to keep: Copilot ships a built-in `/review` command, so the standalone review skill is named `quality-review` to stay slash-reachable. If you add a skill, avoid Copilot's built-in command names (`/review` is the one that currently affects Wingspan; `/plan` is **not** a built-in and is fine).

### Validate before you push

Run the same check CI runs, from the repository root:
Expand Down
Loading