mirror of
https://github.com/github/spec-kit
synced 2026-06-21 13:51:39 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| da9911051e | |||
| 07cde6764a | |||
| c7e0cacaff | |||
| 0f9beabca7 | |||
| 69b9348776 | |||
| c47f334629 |
@@ -35,7 +35,7 @@
|
||||
- [🔧 Prerequisites](#-prerequisites)
|
||||
- [📖 Learn More](#-learn-more)
|
||||
- [📋 Detailed Process](#-detailed-process)
|
||||
- [ Support](#-support)
|
||||
- [💬 Support](#-support)
|
||||
- [🙏 Acknowledgements](#-acknowledgements)
|
||||
- [📄 License](#-license)
|
||||
|
||||
@@ -581,7 +581,7 @@ Once the implementation is complete, test the application and resolve any runtim
|
||||
|
||||
---
|
||||
|
||||
## Support
|
||||
## 💬 Support
|
||||
|
||||
For support, please open a [GitHub issue](https://github.com/github/spec-kit/issues/new). We welcome bug reports, feature requests, and questions about using Spec-Driven Development.
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ The following community-contributed presets customize how Spec Kit behaves — o
|
||||
| Multi-Repo Branching | Coordinates feature branch creation across multiple git repositories (independent repos and submodules) during plan and tasks phases | 2 commands | — | [spec-kit-preset-multi-repo-branching](https://github.com/sakitA/spec-kit-preset-multi-repo-branching) |
|
||||
| Pirate Speak (Full) | Transforms all Spec Kit output into pirate speak — specs become "Voyage Manifests", plans become "Battle Plans", tasks become "Crew Assignments" | 6 templates, 9 commands | — | [spec-kit-presets](https://github.com/mnriem/spec-kit-presets) |
|
||||
| Screenwriting | Spec-Driven Development for screenwriting/scriptwriting/tutorials: feature films, television (pilot, episode, limited series), and stage plays. Adapts the Spec Kit workflow to screenplay craft — slug lines, action lines, act breaks, beat sheets, and industry-standard pitch documents. Supports three-act, Save the Cat, TV pilot, network episode, cable/streaming episode, and stage-play structural frameworks. Export to Fountain, FTX, PDF | 26 templates, 32 commands, 1 script | — | [speckit-preset-screenwriting](https://github.com/adaumann/speckit-preset-screenwriting) |
|
||||
| Security Governance | Adds secure development governance: memory-safe-language preference, secure code generation, NIST SSDF, CWE Top 25, OWASP ASVS, SBOM/VEX/SLSA, OpenSSF Scorecard, and EU CRA applicability | 12 templates, 3 commands | — | [spec-kit-preset-security-governance](https://github.com/hindermath/spec-kit-preset-security-governance) |
|
||||
| Security Governance | Adds secure development governance: memory-safe-language preference, secure code generation, NIST SSDF, CWE Top 25, OWASP ASVS, SBOM/AI-SBOM, VEX/SLSA, OpenSSF Scorecard, G7/BSI AI-SBOM target evidence, and EU CRA applicability | 12 templates, 3 commands | — | [spec-kit-preset-security-governance](https://github.com/hindermath/spec-kit-preset-security-governance) |
|
||||
| Spec2Cloud | Spec-driven workflow tuned for shipping to Azure: spec → plan → tasks → implement → deploy | 5 templates, 8 commands | — | [spec2cloud](https://github.com/Azure-Samples/Spec2Cloud) |
|
||||
| Table of Contents Navigation | Adds a navigable Table of Contents to generated spec.md, plan.md, and tasks.md documents | 3 templates, 3 commands | — | [spec-kit-preset-toc-navigation](https://github.com/Quratulain-bilal/spec-kit-preset-toc-navigation) |
|
||||
| VS Code Ask Questions | Enhances the clarify command to use `vscode/askQuestions` for batched interactive questioning. | 1 command | — | [spec-kit-presets](https://github.com/fdcastel/spec-kit-presets) |
|
||||
|
||||
@@ -35,7 +35,7 @@ Replace the script to add project-specific Git initialization steps:
|
||||
## Output
|
||||
|
||||
On success:
|
||||
- `✓ Git repository initialized`
|
||||
- `[OK] Git repository initialized`
|
||||
|
||||
## Graceful Degradation
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ if (Test-Path $configFile) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
# No config file — auto-commit disabled by default
|
||||
# No config file -- auto-commit disabled by default
|
||||
exit 0
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env pwsh
|
||||
# Git-specific common functions for the git extension.
|
||||
# Extracted from scripts/powershell/common.ps1 — contains only git-specific
|
||||
# Extracted from scripts/powershell/common.ps1 -- contains only git-specific
|
||||
# branch validation and detection logic.
|
||||
|
||||
function Test-HasGit {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env pwsh
|
||||
# Git extension: initialize-repo.ps1
|
||||
# Initialize a Git repository with an initial commit.
|
||||
# Customizable — replace this script to add .gitignore templates,
|
||||
# Customizable -- replace this script to add .gitignore templates,
|
||||
# default branch config, git-flow, LFS, signing, etc.
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
@@ -66,4 +66,4 @@ try {
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "✓ Git repository initialized"
|
||||
Write-Host "[OK] Git repository initialized"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"updated_at": "2026-05-05T10:00:00Z",
|
||||
"updated_at": "2026-05-22T00:00:00Z",
|
||||
"catalog_url": "https://raw.githubusercontent.com/github/spec-kit/main/presets/catalog.community.json",
|
||||
"presets": {
|
||||
"a11y-governance": {
|
||||
@@ -472,11 +472,11 @@
|
||||
"security-governance": {
|
||||
"name": "Security Governance",
|
||||
"id": "security-governance",
|
||||
"version": "0.2.0",
|
||||
"description": "Adds secure development governance, MSL preference, ASVS verification, supply-chain transparency, and EU CRA awareness.",
|
||||
"version": "0.3.0",
|
||||
"description": "Adds memory-safe-language preference, secure code generation, ASVS verification, SBOM/AI-SBOM supply-chain transparency, and EU Cyber Resilience Act awareness.",
|
||||
"author": "Thorsten Hindermann",
|
||||
"repository": "https://github.com/hindermath/spec-kit-preset-security-governance",
|
||||
"download_url": "https://github.com/hindermath/spec-kit-preset-security-governance/archive/refs/tags/v0.2.0.zip",
|
||||
"download_url": "https://github.com/hindermath/spec-kit-preset-security-governance/archive/refs/tags/v0.3.0.zip",
|
||||
"homepage": "https://github.com/hindermath/spec-kit-preset-security-governance",
|
||||
"documentation": "https://github.com/hindermath/spec-kit-preset-security-governance/blob/main/README.md",
|
||||
"license": "MIT",
|
||||
@@ -491,11 +491,20 @@
|
||||
"security",
|
||||
"governance",
|
||||
"msl",
|
||||
"ssdf",
|
||||
"asvs",
|
||||
"supply-chain"
|
||||
"supply-chain",
|
||||
"sbom",
|
||||
"ai-sbom",
|
||||
"vex",
|
||||
"slsa",
|
||||
"cwe-top-25",
|
||||
"g7",
|
||||
"bsi",
|
||||
"cra"
|
||||
],
|
||||
"created_at": "2026-04-27T00:00:00Z",
|
||||
"updated_at": "2026-04-27T00:00:00Z"
|
||||
"updated_at": "2026-05-22T00:00:00Z"
|
||||
},
|
||||
"spec2cloud": {
|
||||
"name": "Spec2Cloud",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "specify-cli"
|
||||
version = "0.8.14"
|
||||
version = "0.8.15.dev0"
|
||||
description = "Specify CLI, part of GitHub Spec Kit. A tool to bootstrap your projects for Spec-Driven Development (SDD)."
|
||||
requires-python = ">=3.11"
|
||||
dependencies = [
|
||||
|
||||
@@ -336,10 +336,10 @@ function Get-FeaturePathsEnv {
|
||||
function Test-FileExists {
|
||||
param([string]$Path, [string]$Description)
|
||||
if (Test-Path -Path $Path -PathType Leaf) {
|
||||
Write-Output " ✓ $Description"
|
||||
Write-Output " [OK] $Description"
|
||||
return $true
|
||||
} else {
|
||||
Write-Output " ✗ $Description"
|
||||
Write-Output " [FAIL] $Description"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
@@ -347,10 +347,10 @@ function Test-FileExists {
|
||||
function Test-DirHasFiles {
|
||||
param([string]$Path, [string]$Description)
|
||||
if ((Test-Path -Path $Path -PathType Container) -and (Get-ChildItem -Path $Path -ErrorAction SilentlyContinue | Where-Object { -not $_.PSIsContainer } | Select-Object -First 1)) {
|
||||
Write-Output " ✓ $Description"
|
||||
Write-Output " [OK] $Description"
|
||||
return $true
|
||||
} else {
|
||||
Write-Output " ✗ $Description"
|
||||
Write-Output " [FAIL] $Description"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
@@ -591,7 +591,7 @@ except Exception:
|
||||
|
||||
if ($layerPaths.Count -eq 0) { return $null }
|
||||
|
||||
# If the top (highest-priority) layer is replace, it wins entirely —
|
||||
# If the top (highest-priority) layer is replace, it wins entirely --
|
||||
# lower layers are irrelevant regardless of their strategies.
|
||||
if ($layerStrategies[0] -eq 'replace') {
|
||||
return (Get-Content $layerPaths[0] -Raw)
|
||||
|
||||
@@ -312,7 +312,7 @@ if (-not $DryRun) {
|
||||
if ($AllowExistingBranch) {
|
||||
# If we're already on the branch, continue without another checkout.
|
||||
if ($currentBranch -eq $branchName) {
|
||||
# Already on the target branch — nothing to do
|
||||
# Already on the target branch -- nothing to do
|
||||
} else {
|
||||
# Otherwise switch to the existing branch instead of failing.
|
||||
$switchBranchError = git checkout -q $branchName 2>&1 | Out-String
|
||||
|
||||
@@ -335,10 +335,11 @@ class TestInitIntegrationFlag:
|
||||
_install_shared_infra(project, "sh", force=False)
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert "already exist and were not updated" in captured.out
|
||||
assert "specify init --here --force" in captured.out
|
||||
plain = strip_ansi(captured.out)
|
||||
assert "already exist and were not updated" in plain
|
||||
assert "specify init --here --force" in plain
|
||||
# Rich may wrap long lines; normalize whitespace for the second command
|
||||
normalized = " ".join(captured.out.split())
|
||||
normalized = " ".join(plain.split())
|
||||
assert "specify integration upgrade --force" in normalized
|
||||
|
||||
def test_shared_infra_warns_when_manifest_cannot_be_loaded(self, tmp_path, capsys):
|
||||
|
||||
@@ -7,6 +7,7 @@ import pytest
|
||||
from typer.testing import CliRunner
|
||||
|
||||
from specify_cli import app
|
||||
from tests.conftest import strip_ansi
|
||||
|
||||
|
||||
runner = CliRunner()
|
||||
@@ -49,7 +50,8 @@ def _write_invalid_manifest(project, key):
|
||||
|
||||
|
||||
def _integration_list_row_cells(output: str, key: str) -> list[str]:
|
||||
row = next(line for line in output.splitlines() if line.startswith(f"│ {key}"))
|
||||
plain = strip_ansi(output)
|
||||
row = next(line for line in plain.splitlines() if line.startswith(f"│ {key}"))
|
||||
return [cell.strip() for cell in row.split("│")[1:-1]]
|
||||
|
||||
|
||||
@@ -160,8 +162,9 @@ class TestIntegrationInstall:
|
||||
finally:
|
||||
os.chdir(old_cwd)
|
||||
assert result.exit_code == 0
|
||||
assert "already installed" in result.output
|
||||
normalized = " ".join(result.output.split())
|
||||
plain = strip_ansi(result.output)
|
||||
assert "already installed" in plain
|
||||
normalized = " ".join(plain.split())
|
||||
assert "specify integration upgrade copilot" in normalized
|
||||
assert "already the default integration" in normalized
|
||||
assert "No files were changed" in normalized
|
||||
@@ -197,9 +200,10 @@ class TestIntegrationInstall:
|
||||
finally:
|
||||
os.chdir(old_cwd)
|
||||
assert result.exit_code != 0
|
||||
assert "Installed integrations: copilot" in result.output
|
||||
assert "Default integration: copilot" in result.output
|
||||
normalized = " ".join(result.output.split())
|
||||
plain = strip_ansi(result.output)
|
||||
assert "Installed integrations: copilot" in plain
|
||||
assert "Default integration: copilot" in plain
|
||||
normalized = " ".join(plain.split())
|
||||
assert "To replace the default integration" in normalized
|
||||
assert "specify integration switch claude" in normalized
|
||||
assert "To install 'claude' alongside" in normalized
|
||||
@@ -309,9 +313,10 @@ class TestIntegrationInstall:
|
||||
finally:
|
||||
os.chdir(old_cwd)
|
||||
assert result.exit_code != 0
|
||||
assert "Installed integrations: copilot" in result.output
|
||||
assert "multi-install safe" in result.output
|
||||
normalized = " ".join(result.output.split())
|
||||
plain = strip_ansi(result.output)
|
||||
assert "Installed integrations: copilot" in plain
|
||||
assert "multi-install safe" in plain
|
||||
normalized = " ".join(plain.split())
|
||||
assert "To replace the default integration" in normalized
|
||||
assert "specify integration switch claude" in normalized
|
||||
assert "To install 'claude' alongside" in normalized
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
"""Regression tests for PowerShell 5.1 compatibility (GitHub issue #2680).
|
||||
|
||||
PowerShell 5.1 (built-in on Windows) defaults to the system's legacy encoding
|
||||
when reading .ps1 files. Non-ASCII characters in UTF-8-encoded scripts cause
|
||||
parse errors because multi-byte sequences are misinterpreted as individual bytes.
|
||||
|
||||
These tests ensure that all shipped .ps1 files remain ASCII-only so they work
|
||||
on both PowerShell 5.1 and 7+.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
|
||||
# All directories that contain shipped PowerShell scripts.
|
||||
_PS1_DIRS = [
|
||||
REPO_ROOT / "scripts" / "powershell",
|
||||
REPO_ROOT / "extensions" / "git" / "scripts" / "powershell",
|
||||
]
|
||||
|
||||
|
||||
def _collect_ps1_files():
|
||||
"""Yield all .ps1 files under the known script directories."""
|
||||
for d in _PS1_DIRS:
|
||||
if d.is_dir():
|
||||
yield from sorted(d.rglob("*.ps1"))
|
||||
|
||||
|
||||
_PS1_FILES = list(_collect_ps1_files())
|
||||
|
||||
|
||||
@pytest.mark.parametrize("ps1_file", _PS1_FILES, ids=lambda p: str(p.relative_to(REPO_ROOT)))
|
||||
def test_ps1_file_is_ascii_only(ps1_file: Path):
|
||||
"""Every .ps1 file must contain only ASCII characters (PS 5.1 compat)."""
|
||||
content = ps1_file.read_bytes()
|
||||
non_ascii = [
|
||||
(i, byte)
|
||||
for i, byte in enumerate(content)
|
||||
if byte > 127
|
||||
]
|
||||
assert not non_ascii, (
|
||||
f"{ps1_file.relative_to(REPO_ROOT)} contains non-ASCII bytes "
|
||||
f"(PowerShell 5.1 incompatible): "
|
||||
f"first at byte offset {non_ascii[0][0]} (0x{non_ascii[0][1]:02x})"
|
||||
)
|
||||
|
||||
|
||||
def test_ps1_files_discovered():
|
||||
"""Sanity check: at least the known script files are found."""
|
||||
names = {p.name for p in _PS1_FILES}
|
||||
assert "common.ps1" in names
|
||||
assert "initialize-repo.ps1" in names
|
||||
Reference in New Issue
Block a user