Compare commits

..

1 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
6f237803a2 Initial plan 2026-02-25 18:55:39 +00:00
12 changed files with 42 additions and 525 deletions

View File

@@ -29,7 +29,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v4
with: with:
fetch-depth: 0 # Fetch all history for git info fetch-depth: 0 # Fetch all history for git info

View File

@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v4
- name: Run markdownlint-cli2 - name: Run markdownlint-cli2
uses: DavidAnson/markdownlint-cli2-action@v19 uses: DavidAnson/markdownlint-cli2-action@v19

View File

@@ -19,7 +19,7 @@ jobs:
pull-requests: write pull-requests: write
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v6 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -16,10 +16,10 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Install uv - name: Install uv
uses: astral-sh/setup-uv@v7 uses: astral-sh/setup-uv@v6
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v6 uses: actions/setup-python@v5
with: with:
python-version: "3.13" python-version: "3.13"
@@ -36,10 +36,10 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Install uv - name: Install uv
uses: astral-sh/setup-uv@v7 uses: astral-sh/setup-uv@v6
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6 uses: actions/setup-python@v5
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}

View File

@@ -456,20 +456,18 @@ Users install with:
specify extension add --from https://github.com/.../spec-kit-my-ext-1.0.0.zip specify extension add --from https://github.com/.../spec-kit-my-ext-1.0.0.zip
``` ```
### Option 3: Community Reference Catalog ### Option 3: Extension Catalog (Future)
Submit to the community catalog for public discovery: Submit to official catalog:
1. **Fork** spec-kit repository 1. **Fork** spec-kit repository
2. **Add entry** to `extensions/catalog.community.json` 2. **Add entry** to `extensions/catalog.json`
3. **Update** `extensions/README.md` with your extension 3. **Create PR**
4. **Create PR** following the [Extension Publishing Guide](EXTENSION-PUBLISHING-GUIDE.md) 4. **After merge**, users can install with:
5. **After merge**, your extension becomes available:
- Users can browse `catalog.community.json` to discover your extension
- Users copy the entry to their own `catalog.json`
- Users install with: `specify extension add my-ext` (from their catalog)
See the [Extension Publishing Guide](EXTENSION-PUBLISHING-GUIDE.md) for detailed submission instructions. ```bash
specify extension add my-ext # No URL needed!
```
--- ---

View File

@@ -129,32 +129,26 @@ specify extension add --from https://github.com/your-org/spec-kit-your-extension
## Submit to Catalog ## Submit to Catalog
### Understanding the Catalogs
Spec Kit uses a dual-catalog system. For details about how catalogs work, see the main [Extensions README](README.md#extension-catalogs).
**For extension publishing**: All community extensions should be added to `catalog.community.json`. Users browse this catalog and copy extensions they trust into their own `catalog.json`.
### 1. Fork the spec-kit Repository ### 1. Fork the spec-kit Repository
```bash ```bash
# Fork on GitHub # Fork on GitHub
# https://github.com/github/spec-kit/fork # https://github.com/statsperform/spec-kit/fork
# Clone your fork # Clone your fork
git clone https://github.com/YOUR-USERNAME/spec-kit.git git clone https://github.com/YOUR-USERNAME/spec-kit.git
cd spec-kit cd spec-kit
``` ```
### 2. Add Extension to Community Catalog ### 2. Add Extension to Catalog
Edit `extensions/catalog.community.json` and add your extension: Edit `extensions/catalog.json` and add your extension:
```json ```json
{ {
"schema_version": "1.0", "schema_version": "1.0",
"updated_at": "2026-01-28T15:54:00Z", "updated_at": "2026-01-28T15:54:00Z",
"catalog_url": "https://raw.githubusercontent.com/github/spec-kit/main/extensions/catalog.community.json", "catalog_url": "https://raw.githubusercontent.com/statsperform/spec-kit/main/extensions/catalog.json",
"extensions": { "extensions": {
"your-extension": { "your-extension": {
"name": "Your Extension Name", "name": "Your Extension Name",
@@ -204,25 +198,15 @@ Edit `extensions/catalog.community.json` and add your extension:
- Use current timestamp for `created_at` and `updated_at` - Use current timestamp for `created_at` and `updated_at`
- Update the top-level `updated_at` to current time - Update the top-level `updated_at` to current time
### 3. Update Extensions README ### 3. Submit Pull Request
Add your extension to the Available Extensions table in `extensions/README.md`:
```markdown
| Your Extension Name | Brief description of what it does | [repo-name](https://github.com/your-org/spec-kit-your-extension) |
```
Insert your extension in alphabetical order in the table.
### 4. Submit Pull Request
```bash ```bash
# Create a branch # Create a branch
git checkout -b add-your-extension git checkout -b add-your-extension
# Commit your changes # Commit your changes
git add extensions/catalog.community.json extensions/README.md git add extensions/catalog.json
git commit -m "Add your-extension to community catalog git commit -m "Add your-extension to catalog
- Extension ID: your-extension - Extension ID: your-extension
- Version: 1.0.0 - Version: 1.0.0
@@ -234,7 +218,7 @@ git commit -m "Add your-extension to community catalog
git push origin add-your-extension git push origin add-your-extension
# Create Pull Request on GitHub # Create Pull Request on GitHub
# https://github.com/github/spec-kit/compare # https://github.com/statsperform/spec-kit/compare
``` ```
**Pull Request Template**: **Pull Request Template**:
@@ -259,8 +243,6 @@ Brief description of what your extension does.
- [x] Extension tested on real project - [x] Extension tested on real project
- [x] All commands working - [x] All commands working
- [x] No security vulnerabilities - [x] No security vulnerabilities
- [x] Added to extensions/catalog.community.json
- [x] Added to extensions/README.md Available Extensions table
### Testing ### Testing
Tested on: Tested on:

View File

@@ -76,15 +76,13 @@ vim .specify/extensions/jira/jira-config.yml
## Finding Extensions ## Finding Extensions
**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.
### Browse All Extensions ### Browse All Extensions
```bash ```bash
specify extension search specify extension search
``` ```
Shows all extensions in your organization's catalog. Shows all available extensions in the catalog.
### Search by Keyword ### Search by Keyword
@@ -417,15 +415,11 @@ export SPECKIT_CATALOG_URL="https://example.com/staging/catalog.json"
--- ---
## Extension Catalogs
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).
## Organization Catalog Customization ## Organization Catalog Customization
### Why Customize Your Catalog ### Why the Default Catalog is Empty
Organizations customize their `catalog.json` to: The default spec-kit catalog ships empty by design. This allows organizations to:
- **Control available extensions** - Curate which extensions your team can install - **Control available extensions** - Curate which extensions your team can install
- **Host private extensions** - Internal tools that shouldn't be public - **Host private extensions** - Internal tools that shouldn't be public

View File

@@ -1,74 +1,8 @@
# Spec Kit Extensions # Spec Kit Community Extensions
Extension system for [Spec Kit](https://github.com/github/spec-kit) - add new functionality without bloating the core framework. Community-contributed extensions for [Spec Kit](https://github.com/github/spec-kit).
## Extension Catalogs ## Available Extensions
Spec Kit provides two catalog files with different purposes:
### Your Catalog (`catalog.json`)
- **Purpose**: Default upstream catalog of extensions used by the Spec Kit CLI
- **Default State**: Empty by design in the upstream project - you or your organization populate a fork/copy with extensions you trust
- **Location (upstream)**: `extensions/catalog.json` in the GitHub-hosted spec-kit repo
- **CLI Default**: The `specify extension` commands use the upstream catalog URL by default, unless overridden
- **Org Catalog**: Point `SPECKIT_CATALOG_URL` at your organization's fork or hosted catalog JSON to use it instead of the upstream default
- **Customization**: Copy entries from the community catalog into your org catalog, or add your own extensions directly
**Example override:**
```bash
# Override the default upstream catalog with your organization's catalog
export SPECKIT_CATALOG_URL="https://your-org.com/spec-kit/catalog.json"
specify extension search # Now uses your organization's catalog instead of the upstream default
```
### Community Reference Catalog (`catalog.community.json`)
- **Purpose**: Browse available community-contributed extensions
- **Status**: Active - contains extensions submitted by the community
- **Location**: `extensions/catalog.community.json`
- **Usage**: Reference catalog for discovering available extensions
- **Submission**: Open to community contributions via Pull Request
**How It Works:**
## Making Extensions Available
You control which extensions your team can discover and install:
### Option 1: Curated Catalog (Recommended for Organizations)
Populate your `catalog.json` with approved extensions:
1. **Discover** extensions from various sources:
- Browse `catalog.community.json` for community extensions
- Find private/internal extensions in your organization's repos
- Discover extensions from trusted third parties
2. **Review** extensions and choose which ones you want to make available
3. **Add** those extension entries to your own `catalog.json`
4. **Team members** can now discover and install them:
- `specify extension search` shows your curated catalog
- `specify extension add <name>` installs from your catalog
**Benefits**: Full control over available extensions, team consistency, organizational approval workflow
**Example**: Copy an entry from `catalog.community.json` to your `catalog.json`, then your team can discover and install it by name.
### Option 2: Direct URLs (For Ad-hoc Use)
Skip catalog curation - team members install directly using URLs:
```bash
specify extension add --from https://github.com/org/spec-kit-ext/archive/refs/tags/v1.0.0.zip
```
**Benefits**: Quick for one-off testing or private extensions
**Tradeoff**: Extensions installed this way won't appear in `specify extension search` for other team members unless you also add them to your `catalog.json`.
## Available Community Extensions
The following community-contributed extensions are available in [`catalog.community.json`](catalog.community.json):
| Extension | Purpose | URL | | Extension | Purpose | URL |
|-----------|---------|-----| |-----------|---------|-----|
@@ -77,43 +11,4 @@ The following community-contributed extensions are available in [`catalog.commun
## Adding Your Extension ## Adding Your Extension
### Submission Process See the [Extension Publishing Guide](EXTENSION-PUBLISHING-GUIDE.md) for instructions on how to submit your extension to the community catalog.
To add your extension to the community catalog:
1. **Prepare your extension** following the [Extension Development Guide](EXTENSION-DEVELOPMENT-GUIDE.md)
2. **Create a GitHub release** for your extension
3. **Submit a Pull Request** that:
- Adds your extension to `extensions/catalog.community.json`
- Updates this README with your extension in the Available Extensions table
4. **Wait for review** - maintainers will review and merge if criteria are met
See the [Extension Publishing Guide](EXTENSION-PUBLISHING-GUIDE.md) for detailed step-by-step instructions.
### Submission Checklist
Before submitting, ensure:
- ✅ Valid `extension.yml` manifest
- ✅ Complete README with installation and usage instructions
- ✅ LICENSE file included
- ✅ GitHub release created with semantic version (e.g., v1.0.0)
- ✅ Extension tested on a real project
- ✅ All commands working as documented
## Installing Extensions
Once extensions are available (either in your catalog or via direct URL), install them:
```bash
# From your curated catalog (by name)
specify extension search # See what's in your catalog
specify extension add <extension-name> # Install by name
# Direct from URL (bypasses catalog)
specify extension add --from https://github.com/<org>/<repo>/archive/refs/tags/<version>.zip
# List installed extensions
specify extension list
```
For more information, see the [Extension User Guide](EXTENSION-USER-GUIDE.md).

View File

@@ -858,41 +858,11 @@ def should_execute_hook(hook: dict, config: dict) -> bool:
## Extension Discovery & Catalog ## Extension Discovery & Catalog
### Dual Catalog System ### Central Catalog
Spec Kit uses two catalog files with different purposes:
#### User Catalog (`catalog.json`)
**URL**: `https://raw.githubusercontent.com/github/spec-kit/main/extensions/catalog.json` **URL**: `https://raw.githubusercontent.com/github/spec-kit/main/extensions/catalog.json`
- **Purpose**: Organization's curated catalog of approved extensions **Format**:
- **Default State**: Empty by design - users populate with extensions they trust
- **Usage**: Default catalog used by `specify extension` CLI commands
- **Control**: Organizations maintain their own fork/version for their teams
#### Community Reference Catalog (`catalog.community.json`)
**URL**: `https://raw.githubusercontent.com/github/spec-kit/main/extensions/catalog.community.json`
- **Purpose**: Reference catalog of available community-contributed extensions
- **Verification**: Community extensions may have `verified: false` initially
- **Status**: Active - open for community contributions
- **Submission**: Via Pull Request following the Extension Publishing Guide
- **Usage**: Browse to discover extensions, then copy to your `catalog.json`
**How It Works:**
1. **Discover**: Browse `catalog.community.json` to find available extensions
2. **Review**: Evaluate extensions for security, quality, and organizational fit
3. **Curate**: Copy approved extension entries from community catalog to your `catalog.json`
4. **Install**: Use `specify extension add <name>` (pulls from your curated catalog)
This approach gives organizations full control over which extensions are available to their teams while maintaining a shared community resource for discovery.
### Catalog Format
**Format** (same for both catalogs):
```json ```json
{ {
@@ -961,52 +931,25 @@ specify extension info jira
### Custom Catalogs ### Custom Catalogs
**⚠️ FUTURE FEATURE - NOT YET IMPLEMENTED** Organizations can host private catalogs:
The following catalog management commands are proposed design concepts but are not yet available in the current implementation:
```bash ```bash
# Add custom catalog (FUTURE - NOT AVAILABLE) # Add custom catalog
specify extension add-catalog https://internal.company.com/spec-kit/catalog.json specify extension add-catalog https://internal.company.com/spec-kit/catalog.json
# Set as default (FUTURE - NOT AVAILABLE) # Set as default
specify extension set-catalog --default https://internal.company.com/spec-kit/catalog.json specify extension set-catalog --default https://internal.company.com/spec-kit/catalog.json
# List catalogs (FUTURE - NOT AVAILABLE) # List catalogs
specify extension catalogs specify extension catalogs
``` ```
**Proposed catalog priority** (future design): **Catalog priority**:
1. Project-specific catalog (`.specify/extension-catalogs.yml`) - *not implemented* 1. Project-specific catalog (`.specify/extension-catalogs.yml`)
2. User-level catalog (`~/.specify/extension-catalogs.yml`) - *not implemented* 2. User-level catalog (`~/.specify/extension-catalogs.yml`)
3. Default GitHub catalog 3. Default GitHub catalog
#### Current Implementation: SPECKIT_CATALOG_URL
**The currently available method** for using custom catalogs is the `SPECKIT_CATALOG_URL` environment variable:
```bash
# Point to your organization's catalog
export SPECKIT_CATALOG_URL="https://internal.company.com/spec-kit/catalog.json"
# All extension commands now use your custom catalog
specify extension search # Uses custom catalog
specify extension add jira # Installs from custom catalog
```
**Requirements:**
- URL must use HTTPS (HTTP only allowed for localhost testing)
- Catalog must follow the standard catalog.json schema
- Must be publicly accessible or accessible within your network
**Example for testing:**
```bash
# Test with localhost during development
export SPECKIT_CATALOG_URL="http://localhost:8000/catalog.json"
specify extension search
```
--- ---
## CLI Commands ## CLI Commands

View File

@@ -351,19 +351,10 @@ create_new_agent_file() {
# Convert \n sequences to actual newlines # Convert \n sequences to actual newlines
newline=$(printf '\n') newline=$(printf '\n')
sed -i.bak2 "s/\\\\n/${newline}/g" "$temp_file" sed -i.bak2 "s/\\\\n/${newline}/g" "$temp_file"
# Clean up backup files # Clean up backup files
rm -f "$temp_file.bak" "$temp_file.bak2" rm -f "$temp_file.bak" "$temp_file.bak2"
# Prepend Cursor frontmatter for .mdc files so rules are auto-included
if [[ "$target_file" == *.mdc ]]; then
local frontmatter_file
frontmatter_file=$(mktemp) || return 1
printf '%s\n' "---" "description: Project Development Guidelines" "globs: [\"**/*\"]" "alwaysApply: true" "---" "" > "$frontmatter_file"
cat "$temp_file" >> "$frontmatter_file"
mv "$frontmatter_file" "$temp_file"
fi
return 0 return 0
} }
@@ -501,24 +492,13 @@ update_existing_agent_file() {
changes_entries_added=true changes_entries_added=true
fi fi
# Ensure Cursor .mdc files have YAML frontmatter for auto-inclusion
if [[ "$target_file" == *.mdc ]]; then
if ! head -1 "$temp_file" | grep -q '^---'; then
local frontmatter_file
frontmatter_file=$(mktemp) || { rm -f "$temp_file"; return 1; }
printf '%s\n' "---" "description: Project Development Guidelines" "globs: [\"**/*\"]" "alwaysApply: true" "---" "" > "$frontmatter_file"
cat "$temp_file" >> "$frontmatter_file"
mv "$frontmatter_file" "$temp_file"
fi
fi
# Move temp file to target atomically # Move temp file to target atomically
if ! mv "$temp_file" "$target_file"; then if ! mv "$temp_file" "$target_file"; then
log_error "Failed to update target file" log_error "Failed to update target file"
rm -f "$temp_file" rm -f "$temp_file"
return 1 return 1
fi fi
return 0 return 0
} }
#============================================================================== #==============================================================================

View File

@@ -258,12 +258,6 @@ function New-AgentFile {
# Convert literal \n sequences introduced by Escape to real newlines # Convert literal \n sequences introduced by Escape to real newlines
$content = $content -replace '\\n',[Environment]::NewLine $content = $content -replace '\\n',[Environment]::NewLine
# Prepend Cursor frontmatter for .mdc files so rules are auto-included
if ($TargetFile -match '\.mdc$') {
$frontmatter = @('---','description: Project Development Guidelines','globs: ["**/*"]','alwaysApply: true','---','') -join [Environment]::NewLine
$content = $frontmatter + $content
}
$parent = Split-Path -Parent $TargetFile $parent = Split-Path -Parent $TargetFile
if (-not (Test-Path $parent)) { New-Item -ItemType Directory -Path $parent | Out-Null } if (-not (Test-Path $parent)) { New-Item -ItemType Directory -Path $parent | Out-Null }
Set-Content -LiteralPath $TargetFile -Value $content -NoNewline -Encoding utf8 Set-Content -LiteralPath $TargetFile -Value $content -NoNewline -Encoding utf8
@@ -340,12 +334,6 @@ function Update-ExistingAgentFile {
$newTechEntries | ForEach-Object { $output.Add($_) } $newTechEntries | ForEach-Object { $output.Add($_) }
} }
# Ensure Cursor .mdc files have YAML frontmatter for auto-inclusion
if ($TargetFile -match '\.mdc$' -and $output.Count -gt 0 -and $output[0] -ne '---') {
$frontmatter = @('---','description: Project Development Guidelines','globs: ["**/*"]','alwaysApply: true','---','')
$output.InsertRange(0, $frontmatter)
}
Set-Content -LiteralPath $TargetFile -Value ($output -join [Environment]::NewLine) -Encoding utf8 Set-Content -LiteralPath $TargetFile -Value ($output -join [Environment]::NewLine) -Encoding utf8
return $true return $true
} }

View File

@@ -1,263 +0,0 @@
"""
Tests for Cursor .mdc frontmatter generation (issue #669).
Verifies that update-agent-context.sh properly prepends YAML frontmatter
to .mdc files so that Cursor IDE auto-includes the rules.
"""
import os
import shutil
import subprocess
import textwrap
import pytest
SCRIPT_PATH = os.path.join(
os.path.dirname(__file__),
os.pardir,
"scripts",
"bash",
"update-agent-context.sh",
)
EXPECTED_FRONTMATTER_LINES = [
"---",
"description: Project Development Guidelines",
'globs: ["**/*"]',
"alwaysApply: true",
"---",
]
requires_git = pytest.mark.skipif(
shutil.which("git") is None,
reason="git is not installed",
)
class TestScriptFrontmatterPattern:
"""Static analysis — no git required."""
def test_create_new_has_mdc_frontmatter_logic(self):
"""create_new_agent_file() must contain .mdc frontmatter logic."""
with open(SCRIPT_PATH, encoding="utf-8") as f:
content = f.read()
assert 'if [[ "$target_file" == *.mdc ]]' in content
assert "alwaysApply: true" in content
def test_update_existing_has_mdc_frontmatter_logic(self):
"""update_existing_agent_file() must also handle .mdc frontmatter."""
with open(SCRIPT_PATH, encoding="utf-8") as f:
content = f.read()
# There should be two occurrences of the .mdc check — one per function
occurrences = content.count('if [[ "$target_file" == *.mdc ]]')
assert occurrences >= 2, (
f"Expected at least 2 .mdc frontmatter checks, found {occurrences}"
)
def test_powershell_script_has_mdc_frontmatter_logic(self):
"""PowerShell script must also handle .mdc frontmatter."""
ps_path = os.path.join(
os.path.dirname(__file__),
os.pardir,
"scripts",
"powershell",
"update-agent-context.ps1",
)
with open(ps_path, encoding="utf-8") as f:
content = f.read()
assert "alwaysApply: true" in content
occurrences = content.count(r"\.mdc$")
assert occurrences >= 2, (
f"Expected at least 2 .mdc frontmatter checks in PS script, found {occurrences}"
)
@requires_git
class TestCursorFrontmatterIntegration:
"""Integration tests using a real git repo."""
@pytest.fixture
def git_repo(self, tmp_path):
"""Create a minimal git repo with the spec-kit structure."""
repo = tmp_path / "repo"
repo.mkdir()
# Init git repo
subprocess.run(
["git", "init"], cwd=str(repo), capture_output=True, check=True
)
subprocess.run(
["git", "config", "user.email", "test@test.com"],
cwd=str(repo),
capture_output=True,
check=True,
)
subprocess.run(
["git", "config", "user.name", "Test"],
cwd=str(repo),
capture_output=True,
check=True,
)
# Create .specify dir with config
specify_dir = repo / ".specify"
specify_dir.mkdir()
(specify_dir / "config.yaml").write_text(
textwrap.dedent("""\
project_type: webapp
language: python
framework: fastapi
database: N/A
""")
)
# Create template
templates_dir = specify_dir / "templates"
templates_dir.mkdir()
(templates_dir / "agent-file-template.md").write_text(
"# [PROJECT NAME] Development Guidelines\n\n"
"Auto-generated from all feature plans. Last updated: [DATE]\n\n"
"## Active Technologies\n\n"
"[EXTRACTED FROM ALL PLAN.MD FILES]\n\n"
"## Project Structure\n\n"
"[ACTUAL STRUCTURE FROM PLANS]\n\n"
"## Development Commands\n\n"
"[ONLY COMMANDS FOR ACTIVE TECHNOLOGIES]\n\n"
"## Coding Conventions\n\n"
"[LANGUAGE-SPECIFIC, ONLY FOR LANGUAGES IN USE]\n\n"
"## Recent Changes\n\n"
"[LAST 3 FEATURES AND WHAT THEY ADDED]\n"
)
# Create initial commit
subprocess.run(
["git", "add", "-A"], cwd=str(repo), capture_output=True, check=True
)
subprocess.run(
["git", "commit", "-m", "init"],
cwd=str(repo),
capture_output=True,
check=True,
)
# Create a feature branch so CURRENT_BRANCH detection works
subprocess.run(
["git", "checkout", "-b", "001-test-feature"],
cwd=str(repo),
capture_output=True,
check=True,
)
# Create a spec so the script detects the feature
spec_dir = repo / "specs" / "001-test-feature"
spec_dir.mkdir(parents=True)
(spec_dir / "plan.md").write_text(
"# Test Feature Plan\n\n"
"## Technology Stack\n\n"
"- Language: Python\n"
"- Framework: FastAPI\n"
)
return repo
def _run_update(self, repo, agent_type="cursor-agent"):
"""Run update-agent-context.sh for a specific agent type."""
script = os.path.abspath(SCRIPT_PATH)
result = subprocess.run(
["bash", script, agent_type],
cwd=str(repo),
capture_output=True,
text=True,
timeout=30,
)
return result
def test_new_mdc_file_has_frontmatter(self, git_repo):
"""Creating a new .mdc file must include YAML frontmatter."""
result = self._run_update(git_repo)
assert result.returncode == 0, f"Script failed: {result.stderr}"
mdc_file = git_repo / ".cursor" / "rules" / "specify-rules.mdc"
assert mdc_file.exists(), "Cursor .mdc file was not created"
content = mdc_file.read_text()
lines = content.splitlines()
# First line must be the opening ---
assert lines[0] == "---", f"Expected frontmatter start, got: {lines[0]}"
# Check all frontmatter lines are present
for expected in EXPECTED_FRONTMATTER_LINES:
assert expected in content, f"Missing frontmatter line: {expected}"
# Content after frontmatter should be the template content
assert "Development Guidelines" in content
def test_existing_mdc_without_frontmatter_gets_it_added(self, git_repo):
"""Updating an existing .mdc file that lacks frontmatter must add it."""
# First, create the file WITHOUT frontmatter (simulating pre-fix state)
cursor_dir = git_repo / ".cursor" / "rules"
cursor_dir.mkdir(parents=True, exist_ok=True)
mdc_file = cursor_dir / "specify-rules.mdc"
mdc_file.write_text(
"# repo Development Guidelines\n\n"
"Auto-generated from all feature plans. Last updated: 2025-01-01\n\n"
"## Active Technologies\n\n"
"- Python + FastAPI (main)\n\n"
"## Recent Changes\n\n"
"- main: Added Python + FastAPI\n"
)
result = self._run_update(git_repo)
assert result.returncode == 0, f"Script failed: {result.stderr}"
content = mdc_file.read_text()
lines = content.splitlines()
assert lines[0] == "---", f"Expected frontmatter start, got: {lines[0]}"
for expected in EXPECTED_FRONTMATTER_LINES:
assert expected in content, f"Missing frontmatter line: {expected}"
def test_existing_mdc_with_frontmatter_not_duplicated(self, git_repo):
"""Updating an .mdc file that already has frontmatter must not duplicate it."""
cursor_dir = git_repo / ".cursor" / "rules"
cursor_dir.mkdir(parents=True, exist_ok=True)
mdc_file = cursor_dir / "specify-rules.mdc"
frontmatter = (
"---\n"
"description: Project Development Guidelines\n"
'globs: ["**/*"]\n'
"alwaysApply: true\n"
"---\n\n"
)
body = (
"# repo Development Guidelines\n\n"
"Auto-generated from all feature plans. Last updated: 2025-01-01\n\n"
"## Active Technologies\n\n"
"- Python + FastAPI (main)\n\n"
"## Recent Changes\n\n"
"- main: Added Python + FastAPI\n"
)
mdc_file.write_text(frontmatter + body)
result = self._run_update(git_repo)
assert result.returncode == 0, f"Script failed: {result.stderr}"
content = mdc_file.read_text()
# Count occurrences of the frontmatter delimiter
assert content.count("alwaysApply: true") == 1, (
"Frontmatter was duplicated"
)
def test_non_mdc_file_has_no_frontmatter(self, git_repo):
"""Non-.mdc agent files (e.g., Claude) must NOT get frontmatter."""
result = self._run_update(git_repo, agent_type="claude")
assert result.returncode == 0, f"Script failed: {result.stderr}"
claude_file = git_repo / ".claude" / "CLAUDE.md"
if claude_file.exists():
content = claude_file.read_text()
assert not content.startswith("---"), (
"Non-mdc file should not have frontmatter"
)