Skip to content
Merged
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
114 changes: 114 additions & 0 deletions .github/workflows/release-extension.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
name: Release Extension

on:
pull_request:
branches: [main]
paths:
- 'spec-kit-extensions/**'
- '.github/workflows/release-extension.yml'
push:
branches: [main]
paths:
- 'spec-kit-extensions/**'
- '.github/workflows/release-extension.yml'
tags:
- 'extension/canvas-design/v*'
permissions:
contents: read

jobs:
package:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- uses: actions/setup-python@v5
with:
python-version: '3.12'

- name: Install package test dependencies
run: python -m pip install -r spec-kit-extensions/tests/requirements.txt

- name: Validate release version
run: |
python - <<'PY'
import json
import os
import re
import yaml

with open("spec-kit-extensions/canvas-design/extension.yml") as source:
version = yaml.safe_load(source)["extension"]["version"]
if not re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+", version):
raise SystemExit(f"Invalid manifest version: {version}")
tag = f"extension/canvas-design/v{version}"
with open("spec-kit-extensions/catalog.json") as source:
entry = json.load(source)["extensions"]["canvas-design"]
if entry["version"] != version:
raise SystemExit("Catalog version must match extension.yml version")
download_url = f"https://github.com/github/spec-kit-copilot/releases/download/{tag}/canvas-design.zip"
if entry["download_url"] != download_url:
raise SystemExit("Catalog download URL must match the release asset")
if os.environ["GITHUB_REF"].startswith("refs/tags/"):
if os.environ["GITHUB_REF"] != f"refs/tags/{tag}":
raise SystemExit("Release tag must match extension.yml version")
PY

- name: Test package contracts
run: python -m unittest discover -s spec-kit-extensions/tests -v

- name: Create extension ZIP
run: |
python - <<'PY'
from pathlib import Path
from zipfile import ZIP_DEFLATED, ZipFile

package = Path("spec-kit-extensions/canvas-design")
with ZipFile("canvas-design.zip", "w", ZIP_DEFLATED) as archive:
for path in sorted(package.rglob("*")):
if path.is_symlink():
raise SystemExit(f"Cannot package symlink: {path}")
if path.is_file():
archive.write(path, path.relative_to(package).as_posix())
PY

- name: Verify release archive
env:
CANVAS_DESIGN_ARCHIVE: canvas-design.zip
run: python -m unittest discover -s spec-kit-extensions/tests -v

- name: Upload validated archive
uses: actions/upload-artifact@v4
with:
name: canvas-design-package
path: canvas-design.zip
if-no-files-found: error

release:
needs: package
if: startsWith(github.ref, 'refs/tags/extension/canvas-design/v')
runs-on: ubuntu-latest
permissions:
contents: write
concurrency:
group: canvas-design-release
cancel-in-progress: false
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- uses: actions/download-artifact@v4
with:
name: canvas-design-package

- name: Publish validated extension
env:
GH_TOKEN: ${{ github.token }}
run: |
TAG="${GITHUB_REF#refs/tags/}"
gh release create "$TAG" canvas-design.zip \
--verify-tag \
--latest=false \
--title "Canvas Design ${TAG##*/}" \
--notes "Specify CLI extension: page templates and the load-page command. Requires a separate compatible Designer provider exposing speckit_designer_load_pages; this package does not ship a Designer."
8 changes: 8 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,14 @@ toolchains:
When revving a preset, bump `preset.yml` + the `catalog.json` entry together
**before** tagging.

## Spec Kit extensions (`spec-kit-extensions/`)

This directory and its `catalog.json` hold **Copilot-specific Specify CLI
extensions**, parallel to the preset catalog. Entries must depend on Copilot
tools or providers; do not import general-purpose extensions or add these packages
to the Copilot plugin marketplace. Keep each catalog entry's version, requirements,
and release URL aligned with its `extension.yml` and package README.

## When revving the core skills plugin

1. Re-enumerate the `specify` CLI surface for the **latest** release
Expand Down
1 change: 1 addition & 0 deletions spec-kit-extensions/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__pycache__/
32 changes: 32 additions & 0 deletions spec-kit-extensions/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Copilot-specific Spec Kit extensions

This catalog contains extensions that depend on Copilot-specific tools or
providers, not general-purpose Spec Kit extensions. These packages are consumed
by the **Specify CLI** (`specify extension add`), not by the Copilot plugin
marketplace. Their `catalog.json` and `extension.yml` manifests live here;
Copilot canvas providers remain under `plugins/`.

- [Canvas Design](canvas-design/README.md) registers JSON settings pages for
a compatible Canvas Designer provider. It does not ship that provider or
register a Copilot marketplace entry.

## Installation

Register the catalog once, then install by ID:

```powershell
specify extension catalog add https://raw.githubusercontent.com/github/spec-kit-copilot/main/spec-kit-extensions/catalog.json --name spec-kit-copilot --install-allowed
specify extension add canvas-design
```

Catalogs are discovery-only by default; `--install-allowed` permits installation.
The referenced release ZIP must be published before installation can succeed.
See the package README for provider requirements and direct-URL installation.

## Versioning and releases

Each extension is versioned independently in its `extension.yml`. Update the
manifest, catalog entry, and package README version together.

The **Release Extension** workflow validates and publishes Canvas Design as
`canvas-design.zip` when an `extension/canvas-design/vX.Y.Z` tag is pushed.
186 changes: 186 additions & 0 deletions spec-kit-extensions/canvas-design/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
# Canvas Design

Canvas Design **0.1.0** is a Spec Kit extension requiring Specify CLI **>=1.0.7**.
It supplies JSON settings pages for the Canvas Designer; it is not a Copilot
plugin or a canvas provider.

## Installation

From an initialized project, register the Copilot-specific extension catalog
once, then install by ID:

```powershell
specify extension catalog add https://raw.githubusercontent.com/github/spec-kit-copilot/main/spec-kit-extensions/catalog.json --name spec-kit-copilot --install-allowed
specify extension add canvas-design
```

Alternatively, install directly from a published release without registering
the catalog:

```powershell
specify extension add canvas-design --from https://github.com/github/spec-kit-copilot/releases/download/extension/canvas-design/v0.1.0/canvas-design.zip
```

The release ZIP must be published before either installation method can succeed.
For a new Copilot project, initialize it with
`specify init . --integration copilot --integration-options="--skills"` first.
Normal installation copies the package; do not use a development symlink install
for preset composition. Copilot skills mode exposes the command as
`speckit-canvas-design-load-page`. Use `/skills reload` to discover newly installed
or composed skills in the current session.

## Required Designer capability

The package requires a separately installed, compatible Copilot Canvas Designer
provider exposing the **`speckit_designer_load_pages` custom tool** before its
panel opens. That provider/tool is **not shipped by this package**. Installing or
publishing this extension does not create a standalone Designer, and no released
Wizard version is asserted to support this page/tool protocol.

The launching integration must supply a valid `handoffId` and, when reloading,
a `requestId`. The command resolves all pages in that session's project and
submits them together. If the tool is missing, the command must report that and
stop; it must not invent a fallback, run a Python helper, or write provider state.
Only after a successful tool result may the launching integration open
`speckit-canvas-designer` using the same handoff ID.

## Registered pages

| Template name | Page title | Order | Contents |
| --- | --- | --- | --- |
| `canvas-settings-setup` | Essentials | 10 | Canvas ID, Title, Description, Workflow header, Show slug field |
| `canvas-settings-artifacts` | Artifacts | 20 | Empty |
| `canvas-settings-appearance` | Appearance | 30 | Empty |
| `canvas-settings-results` | Result Badges | 40 | Empty |

`commands/load-page.md` declares the initial page set. The agent reads the
**entire preset-composed command**, including all appended "Additional Designer
pages" sections, and resolves each name with `specify preset resolve <name>`.
It inspects the CLI output: even exit code zero can report `not found`. Missing
templates, composition warnings and command failures stop the load; there is no
fallback to the extension's default file.

The agent submits the complete set of `{name, path}` pairs once to
`speckit_designer_load_pages`, a **custom tool registered by the Copilot Designer
provider**, available before opening its panel. This is not a built-in Specify
command or a Python script. A compatible provider is responsible for validating
the input and storing the resulting model; this package supplies only the
command, page templates, and schema. The agent is responsible for using the
CLI-selected paths, not independently reconstructing template precedence.

Page JSON defines its full template `id`, title, description, order, enabled state, and fields.
Enabled pages sort by order, then page ID. Fields support strings and booleans;
omitting `type` means string. A `default` is allowed only with an explicit
`"type": "boolean"`. Field IDs must be unique across enabled pages. Canvas ID and Title must remain
present. The identity fields retain their built-in constraints even when a
preset changes their labels or placement. Each file must conform to
`schemas/page.schema.json`, have an `id` equal to its supplied template name,
and resolve to a regular `.json` file inside the session project's `.specify/`.
Invalid names, escaping symlinks, unsupported controls, duplicate fields, invalid
JSON and oversized files reject the entire batch without replacing the last
valid model.
The compatible provider must enforce limits of 100 pages, 256 KiB per file,
and a 2 MiB saved model. These batch, path, uniqueness, and identity constraints
are provider requirements beyond the per-file JSON schema; they are not enforced
by installing this package alone.

## Customize pages with a preset

To replace Appearance, declare a JSON template in your `preset.yml`:

```yaml
schema_version: "1.0"
preset:
id: copilot-canvas-appearance
name: Copilot Canvas Appearance
version: "1.0.0"
description: Replace the Designer Appearance page.
requires:
speckit_version: ">=1.0.7"
extensions: [canvas-design]
provides:
templates:
- type: template
name: canvas-settings-appearance
file: pages/appearance.json
strategy: replace
```

The JSON `id` must be `canvas-settings-appearance`. Use a small local preset for
project-level changes: `.specify/templates/overrides/` expects Markdown files,
which the JSON loader rejects.

To add a new page, declare both its JSON template and an appended command
contribution in the preset:

```yaml
schema_version: "1.0"
preset:
id: copilot-canvas-accessibility
name: Copilot Canvas Accessibility
version: "1.0.0"
description: Add a Designer Accessibility page.
requires:
speckit_version: ">=1.0.7"
extensions: [canvas-design]
provides:
templates:
- type: template
name: canvas-settings-accessibility
file: pages/accessibility.json
strategy: replace
- type: command
name: speckit.canvas-design.load-page
file: commands/add-pages.md
strategy: append
```

`commands/add-pages.md` contains instructions, without frontmatter:

```markdown
## Additional Designer pages

- canvas-settings-accessibility
```

`pages/accessibility.json` contains, for example:

```json
{
"schemaVersion": 1,
"id": "canvas-settings-accessibility",
"title": "Accessibility",
"order": 50,
"enabled": true,
"fields": []
}
```

Append **command instructions**, not JSON content. Merely dropping a JSON file
into a directory or registering an additional template does not add it to the
command's page set. There is no automatic artifact discovery or package watcher.
After installing or removing a preset, refresh the composed skill with
`/skills reload` and use the compatible provider's explicit reload flow.
Artifacts, Appearance, and Result Badges are empty page templates. This package
does not implement UI persistence, canvas generation, or result evaluation.

## Tests

From the repository root, with Python 3.12 or later:

```powershell
python -m pip install -r spec-kit-extensions\tests\requirements.txt
python -m unittest discover -s spec-kit-extensions\tests -v
```

These focused package tests cover manifest/catalog agreement, discovery tags,
shipped files, JSON schema acceptance/rejection, default page shape, and the
agent command contract.
The release workflow builds the ZIP inline with `extension.yml` at its root and
reruns the tests with `CANVAS_DESIGN_ARCHIVE` set to the archive path, checking the
exact member set and bytes. Set that environment variable to check a local ZIP.
No Wizard dependencies or provider are needed for these checks.

Real Specify normal-install/preset-composition tests and consumer migration are
a separate follow-up. These package checks do not claim that the full
CLI/provider integration matrix has passed.
Loading
Loading