Skip to content

Commit 408a778

Browse files
Copilotmnriem
andcommitted
docs: update RFC, user guide, and API reference for multi-catalog support
- RFC: replace FUTURE FEATURE section with full implementation docs, add catalog stack resolution order, config file examples, merge conflict resolution, and install_allowed behavior - EXTENSION-USER-GUIDE.md: add multi-catalog section with CLI examples for catalogs/catalog-add/catalog-remove, update catalog config docs - EXTENSION-API-REFERENCE.md: add CatalogEntry class docs, update ExtensionCatalog docs with new methods and result annotations, add catalog CLI commands (catalogs, catalog add, catalog remove) Also fix extension_catalogs command to correctly show "Using built-in default catalog stack" when config file exists but has empty catalogs Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com>
1 parent f41c5d5 commit 408a778

4 files changed

Lines changed: 278 additions & 44 deletions

File tree

extensions/EXTENSION-API-REFERENCE.md

Lines changed: 105 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,32 @@ manager.check_compatibility(
243243
) # Raises: CompatibilityError if incompatible
244244
```
245245

246+
### CatalogEntry
247+
248+
**Module**: `specify_cli.extensions`
249+
250+
Represents a single catalog in the active catalog stack.
251+
252+
```python
253+
from specify_cli.extensions import CatalogEntry
254+
255+
entry = CatalogEntry(
256+
url="https://example.com/catalog.json",
257+
name="org-approved",
258+
priority=1,
259+
install_allowed=True,
260+
)
261+
```
262+
263+
**Fields**:
264+
265+
| Field | Type | Description |
266+
|-------|------|-------------|
267+
| `url` | `str` | Catalog URL (must use HTTPS, or HTTP for localhost) |
268+
| `name` | `str` | Human-readable catalog name |
269+
| `priority` | `int` | Sort order (lower = higher priority, wins on conflicts) |
270+
| `install_allowed` | `bool` | Whether extensions from this catalog can be installed |
271+
246272
### ExtensionCatalog
247273

248274
**Module**: `specify_cli.extensions`
@@ -253,30 +279,65 @@ from specify_cli.extensions import ExtensionCatalog
253279
catalog = ExtensionCatalog(project_root)
254280
```
255281

282+
**Class attributes**:
283+
284+
```python
285+
ExtensionCatalog.DEFAULT_CATALOG_URL # org-approved catalog URL
286+
ExtensionCatalog.COMMUNITY_CATALOG_URL # community catalog URL
287+
```
288+
256289
**Methods**:
257290

258291
```python
259-
# Fetch catalog
292+
# Get the ordered list of active catalogs
293+
entries = catalog.get_active_catalogs() # List[CatalogEntry]
294+
295+
# Fetch catalog (primary catalog, backward compat)
260296
catalog_data = catalog.fetch_catalog(force_refresh: bool = False) # Dict
261297
262-
# Search extensions
298+
# Search extensions across all active catalogs
299+
# Each result includes _catalog_name and _install_allowed
263300
results = catalog.search(
264301
query: Optional[str] = None,
265302
tag: Optional[str] = None,
266303
author: Optional[str] = None,
267304
verified_only: bool = False
268-
) # Returns: List[Dict]
305+
) # Returns: List[Dict] — each dict includes _catalog_name, _install_allowed
269306
270-
# Get extension info
307+
# Get extension info (searches all active catalogs)
308+
# Returns None if not found; includes _catalog_name and _install_allowed
271309
ext_info = catalog.get_extension_info(extension_id: str) # Optional[Dict]
272310
273-
# Check cache validity
311+
# Check cache validity (primary catalog)
274312
is_valid = catalog.is_cache_valid() # bool
275313
276-
# Clear cache
314+
# Clear all catalog caches
277315
catalog.clear_cache()
278316
```
279317

318+
**Result annotation fields**:
319+
320+
Each extension dict returned by `search()` and `get_extension_info()` includes:
321+
322+
| Field | Type | Description |
323+
|-------|------|-------------|
324+
| `_catalog_name` | `str` | Name of the source catalog |
325+
| `_install_allowed` | `bool` | Whether installation is allowed from this catalog |
326+
327+
**Catalog config file** (`.specify/extension-catalogs.yml`):
328+
329+
```yaml
330+
catalogs:
331+
- name: "org-approved"
332+
url: "https://example.com/catalog.json"
333+
priority: 1
334+
install_allowed: true
335+
- name: "community"
336+
url: "https://raw.githubusercontent.com/github/spec-kit/main/extensions/catalog.community.json"
337+
priority: 2
338+
install_allowed: false
339+
```
340+
280341
### HookExecutor
281342

282343
**Module**: `specify_cli.extensions`
@@ -543,6 +604,38 @@ EXECUTE_COMMAND: {command}
543604

544605
**Output**: List of installed extensions with metadata
545606

607+
### extension catalogs
608+
609+
**Usage**: `specify extension catalogs`
610+
611+
Lists all active catalogs in the current catalog stack, showing name, URL, priority, and `install_allowed` status.
612+
613+
### extension catalog add
614+
615+
**Usage**: `specify extension catalog add URL [OPTIONS]`
616+
617+
**Options**:
618+
619+
- `--name NAME` - Catalog name (required)
620+
- `--priority INT` - Priority (lower = higher priority, default: 10)
621+
- `--install-allowed / --no-install-allowed` - Allow installs from this catalog (default: false)
622+
623+
**Arguments**:
624+
625+
- `URL` - Catalog URL (must use HTTPS)
626+
627+
Adds a catalog entry to `.specify/extension-catalogs.yml`.
628+
629+
### extension catalog remove
630+
631+
**Usage**: `specify extension catalog remove NAME`
632+
633+
**Arguments**:
634+
635+
- `NAME` - Catalog name to remove
636+
637+
Removes a catalog entry from `.specify/extension-catalogs.yml`.
638+
546639
### extension add
547640

548641
**Usage**: `specify extension add EXTENSION [OPTIONS]`
@@ -551,13 +644,13 @@ EXECUTE_COMMAND: {command}
551644

552645
- `--from URL` - Install from custom URL
553646
- `--dev PATH` - Install from local directory
554-
- `--version VERSION` - Install specific version
555-
- `--no-register` - Skip command registration
556647

557648
**Arguments**:
558649

559650
- `EXTENSION` - Extension name or URL
560651

652+
**Note**: Extensions from catalogs with `install_allowed: false` cannot be installed via this command.
653+
561654
### extension remove
562655

563656
**Usage**: `specify extension remove EXTENSION [OPTIONS]`
@@ -575,6 +668,8 @@ EXECUTE_COMMAND: {command}
575668

576669
**Usage**: `specify extension search [QUERY] [OPTIONS]`
577670

671+
Searches all active catalogs simultaneously. Results include source catalog name and install_allowed status.
672+
578673
**Options**:
579674

580675
- `--tag TAG` - Filter by tag
@@ -589,6 +684,8 @@ EXECUTE_COMMAND: {command}
589684

590685
**Usage**: `specify extension info EXTENSION`
591686

687+
Shows source catalog and install_allowed status.
688+
592689
**Arguments**:
593690

594691
- `EXTENSION` - Extension ID

extensions/EXTENSION-USER-GUIDE.md

Lines changed: 89 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -76,15 +76,15 @@ vim .specify/extensions/jira/jira-config.yml
7676

7777
## Finding Extensions
7878

79-
**Note**: By default, `specify extension search` uses your organization's catalog (`catalog.json`). If the catalog is empty, you won't see any results. See [Extension Catalogs](#extension-catalogs) to learn how to populate your catalog from the community reference catalog.
79+
`specify extension search` searches **all active catalogs** simultaneously, including the community catalog by default. Results are annotated with their source catalog and install status.
8080

8181
### Browse All Extensions
8282

8383
```bash
8484
specify extension search
8585
```
8686

87-
Shows all extensions in your organization's catalog.
87+
Shows all extensions across all active catalogs (org-approved and community by default).
8888

8989
### Search by Keyword
9090

@@ -402,13 +402,13 @@ In addition to extension-specific environment variables (`SPECKIT_{EXT_ID}_*`),
402402

403403
| Variable | Description | Default |
404404
|----------|-------------|---------|
405-
| `SPECKIT_CATALOG_URL` | Override the extension catalog URL | GitHub-hosted catalog |
405+
| `SPECKIT_CATALOG_URL` | Override the full catalog stack with a single URL (backward compat) | Built-in default stack |
406406
| `GH_TOKEN` / `GITHUB_TOKEN` | GitHub API token for downloads | None |
407407

408408
#### Example: Using a custom catalog for testing
409409

410410
```bash
411-
# Point to a local or alternative catalog
411+
# Point to a local or alternative catalog (replaces the full stack)
412412
export SPECKIT_CATALOG_URL="http://localhost:8000/catalog.json"
413413
414414
# Or use a staging catalog
@@ -419,13 +419,73 @@ export SPECKIT_CATALOG_URL="https://example.com/staging/catalog.json"
419419

420420
## Extension Catalogs
421421

422-
For information about how Spec Kit's dual-catalog system works (`catalog.json` vs `catalog.community.json`), see the main [Extensions README](README.md#extension-catalogs).
422+
Spec Kit uses a **catalog stack** — an ordered list of catalogs searched simultaneously. By default, two catalogs are active:
423+
424+
| Priority | Catalog | Install Allowed | Purpose |
425+
|----------|---------|-----------------|---------|
426+
| 1 | `catalog.json` (org-approved) | ✅ Yes | Extensions your org approves for installation |
427+
| 2 | `catalog.community.json` (community) | ❌ No (discovery only) | Browse community extensions |
428+
429+
### Listing Active Catalogs
430+
431+
```bash
432+
specify extension catalogs
433+
```
434+
435+
### Adding a Catalog (Project-scoped)
436+
437+
```bash
438+
# Add an internal catalog that allows installs
439+
specify extension catalog add \
440+
--name "internal" \
441+
--priority 2 \
442+
--install-allowed \
443+
https://internal.company.com/spec-kit/catalog.json
444+
445+
# Add a discovery-only catalog
446+
specify extension catalog add \
447+
--name "partner" \
448+
--priority 5 \
449+
https://partner.example.com/spec-kit/catalog.json
450+
```
451+
452+
This creates or updates `.specify/extension-catalogs.yml`.
453+
454+
### Removing a Catalog
455+
456+
```bash
457+
specify extension catalog remove internal
458+
```
459+
460+
### Manual Config File
461+
462+
You can also edit `.specify/extension-catalogs.yml` directly:
463+
464+
```yaml
465+
catalogs:
466+
- name: "org-approved"
467+
url: "https://raw.githubusercontent.com/github/spec-kit/main/extensions/catalog.json"
468+
priority: 1
469+
install_allowed: true
470+
471+
- name: "internal"
472+
url: "https://internal.company.com/spec-kit/catalog.json"
473+
priority: 2
474+
install_allowed: true
475+
476+
- name: "community"
477+
url: "https://raw.githubusercontent.com/github/spec-kit/main/extensions/catalog.community.json"
478+
priority: 3
479+
install_allowed: false
480+
```
481+
482+
A user-level equivalent lives at `~/.specify/extension-catalogs.yml`. Project-level config takes full precedence when present.
423483

424484
## Organization Catalog Customization
425485

426486
### Why Customize Your Catalog
427487

428-
Organizations customize their `catalog.json` to:
488+
Organizations customize their catalogs to:
429489

430490
- **Control available extensions** - Curate which extensions your team can install
431491
- **Host private extensions** - Internal tools that shouldn't be public
@@ -503,24 +563,40 @@ Options for hosting your catalog:
503563

504564
#### 3. Configure Your Environment
505565

506-
##### Option A: Environment variable (recommended for CI/CD)
566+
##### Option A: Catalog stack config file (recommended)
507567

508-
```bash
509-
# In ~/.bashrc, ~/.zshrc, or CI pipeline
510-
export SPECKIT_CATALOG_URL="https://your-org.com/spec-kit/catalog.json"
568+
Add to `.specify/extension-catalogs.yml` in your project:
569+
570+
```yaml
571+
catalogs:
572+
- name: "org-approved"
573+
url: "https://your-org.com/spec-kit/catalog.json"
574+
priority: 1
575+
install_allowed: true
511576
```
512577

513-
##### Option B: Per-project configuration
578+
Or use the CLI:
579+
580+
```bash
581+
specify extension catalog add \
582+
--name "org-approved" \
583+
--install-allowed \
584+
https://your-org.com/spec-kit/catalog.json
585+
```
514586

515-
Create `.env` or set in your shell before running spec-kit commands:
587+
##### Option B: Environment variable (recommended for CI/CD, single-catalog)
516588

517589
```bash
518-
SPECKIT_CATALOG_URL="https://your-org.com/spec-kit/catalog.json" specify extension search
590+
# In ~/.bashrc, ~/.zshrc, or CI pipeline
591+
export SPECKIT_CATALOG_URL="https://your-org.com/spec-kit/catalog.json"
519592
```
520593

521594
#### 4. Verify Configuration
522595

523596
```bash
597+
# List active catalogs
598+
specify extension catalogs
599+
524600
# Search should now show your catalog's extensions
525601
specify extension search
526602

0 commit comments

Comments
 (0)