Skip to content

Commit 26f0407

Browse files
mnriemCopilot
andcommitted
fix: make category free-form, keep effect validated
Category is a free-form string (only validated as non-empty when present), while effect remains restricted to 'read-only' or 'read-write'. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 13e16b5 commit 26f0407

5 files changed

Lines changed: 11 additions & 14 deletions

File tree

.github/skills/add-community-extension/SKILL.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ Use the existing entries as the format template. Required fields:
8989
}
9090
```
9191

92-
**Category**one of: `docs`, `code`, `process`, `integration`, `visibility`
92+
**Category**free-form string; common values: `docs`, `code`, `process`, `integration`, `visibility`
9393
**Effect** — one of: `read-only`, `read-write`
9494

9595
If the extension has optional tool dependencies, add a `"tools"` array inside `"requires"`:
@@ -118,7 +118,7 @@ Determine the category and effect from the extension's behavior:
118118
| <Name> | <Description> | `<category>` | <Effect> | [<repo-name>](<repository-url>) |
119119
```
120120

121-
**Category**one of: `docs`, `code`, `process`, `integration`, `visibility`
121+
**Category**free-form; common values: `docs`, `code`, `process`, `integration`, `visibility`
122122
**Effect**`Read-only` (produces reports only) or `Read+Write` (modifies project files)
123123

124124
### 6. Commit, push, and open PR

docs/community/extensions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
The following community-contributed extensions are available in [`catalog.community.json`](https://github.com/github/spec-kit/blob/main/extensions/catalog.community.json):
99

10-
**Categories:**
10+
**Categories** (common values, but any string is allowed):
1111

1212
- `docs` — reads, validates, or generates spec artifacts
1313
- `code` — reviews, validates, or modifies source code

extensions/template/extension.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ extension:
1414
description: "Brief description of what your extension does"
1515

1616
# CUSTOMIZE: Extension category — describes what the extension operates on
17-
# One of: docs | code | process | integration | visibility
17+
# Common values: docs, code, process, integration, visibility
1818
category: "process"
1919

2020
# CUSTOMIZE: Extension effect — whether it modifies project files

src/specify_cli/extensions.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
})
4242
EXTENSION_COMMAND_NAME_PATTERN = re.compile(r"^speckit\.([a-z0-9-]+)\.([a-z0-9-]+)$")
4343

44-
VALID_CATEGORIES = frozenset({"docs", "code", "process", "integration", "visibility"})
4544
VALID_EFFECTS = frozenset({"read-only", "read-write"})
4645

4746
DEFAULT_HOOK_PRIORITY = 10
@@ -204,12 +203,11 @@ def _validate(self):
204203
except pkg_version.InvalidVersion:
205204
raise ValidationError(f"Invalid version: {ext['version']}")
206205

207-
# Validate optional category field
206+
# Validate optional category field (free-form string)
208207
if "category" in ext:
209-
if ext["category"] not in VALID_CATEGORIES:
208+
if not isinstance(ext["category"], str) or not ext["category"].strip():
210209
raise ValidationError(
211-
f"Invalid extension.category '{ext['category']}': "
212-
f"must be one of {sorted(VALID_CATEGORIES)}"
210+
"Invalid extension.category: must be a non-empty string"
213211
)
214212

215213
# Validate optional effect field

tests/test_extensions.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
CatalogEntry,
2525
CORE_COMMAND_NAMES,
2626
DEFAULT_HOOK_PRIORITY,
27-
VALID_CATEGORIES,
2827
VALID_EFFECTS,
2928
ExtensionManifest,
3029
ExtensionRegistry,
@@ -303,10 +302,10 @@ def test_invalid_version(self, temp_dir, valid_manifest_data):
303302
ExtensionManifest(manifest_path)
304303

305304
def test_valid_category(self, temp_dir, valid_manifest_data):
306-
"""Test manifest with valid category values."""
305+
"""Test manifest with various category values (free-form string)."""
307306
import yaml
308307

309-
for category in ("docs", "code", "process", "integration", "visibility"):
308+
for category in ("docs", "code", "process", "integration", "visibility", "custom-category"):
310309
valid_manifest_data["extension"]["category"] = category
311310
manifest_path = temp_dir / "extension.yml"
312311
with open(manifest_path, 'w') as f:
@@ -327,10 +326,10 @@ def test_valid_effect(self, temp_dir, valid_manifest_data):
327326
assert manifest.effect == effect
328327

329328
def test_invalid_category(self, temp_dir, valid_manifest_data):
330-
"""Test manifest with invalid category raises ValidationError."""
329+
"""Test manifest with empty category raises ValidationError."""
331330
import yaml
332331

333-
valid_manifest_data["extension"]["category"] = "invalid-category"
332+
valid_manifest_data["extension"]["category"] = ""
334333
manifest_path = temp_dir / "extension.yml"
335334
with open(manifest_path, 'w') as f:
336335
yaml.dump(valid_manifest_data, f)

0 commit comments

Comments
 (0)