Skip to content

Commit 1a9e4d1

Browse files
Copilotmnriem
andauthored
feat: Git extension stage 1 — bundled extensions/git with hooks on all core commands (#1941)
* feat: add git extension with hooks on all core commands - Create extensions/git/ with 5 commands: initialize, feature, validate, remote, commit - 18 hooks covering before/after for all 9 core commands - Scripts: create-new-feature, initialize-repo, auto-commit, git-common (bash + powershell) - Configurable: branch_numbering, init_commit_message, per-command auto-commit with custom messages - Add hooks to analyze, checklist, clarify, constitution, taskstoissues command templates - Allow hooks-only extensions (no commands required) - Bundle extension in wheel via pyproject.toml force-include - Resolve bundled extensions locally before catalog lookup - Remove planned-but-unimplemented before/after_commit hook refs - Update extension docs (API ref, dev guide, user guide) - 37 new tests covering manifest, install, all scripts (bash+pwsh), config reading, graceful degradation Stage 1: opt-in via 'specify extension add git'. No auto-install, no changes to specify.md or core git init code. Refs: #841, #1382, #1066, #1791, #1191 * fix: set git identity env vars in extension tests for CI runners * fix: address PR review comments - Fix commands property KeyError for hooks-only extensions - Fix has_git() operator precedence in git-common.sh - Align default commit message to '[Spec Kit] Initial commit' across config-template, extension.yml defaults, and both init scripts - Update README to reflect all 5 commands and 18 hooks * fix: address second round of PR review comments - Add type validation for provides.commands (must be list) and hooks (must be dict) in manifest _validate() - Tighten malformed timestamp detection in git-common.sh to catch 7-digit dates without trailing slug (e.g. 2026031-143022) - Pass REPO_ROOT to has_git/Test-HasGit in create-new-feature scripts - Fix initialize command docs: surface errors on git failures, only skip when git is not installed - Fix commit command docs: 'skips with a warning' not 'silently' - Add tests for commands:null and hooks:list rejection * fix: address third round of PR review comments - Remove scripts frontmatter from command files (CommandRegistrar rewrites ../../scripts/ to .specify/scripts/ which points at core scripts, not extension scripts) - Update speckit.git.commit command to derive event name from hook context rather than using a static example - Clarify that hook argument passthrough works via AI agent context (the agent carries conversation state including user's original feature description) * fix: address fourth round of PR review comments - Validate extension_id against ^[a-z0-9-]+$ in _locate_bundled_extension to prevent path traversal (security fix) - Move defaults under config.defaults in extension.yml to match ConfigManager._get_extension_defaults() schema - Ship git-config.yml in extension directory so it's copied during install (provides.config template isn't materialized by ExtensionManager) - Condition handling in hook templates: intentionally matches existing pattern from specify/plan/tasks/implement templates (not a new issue) * fix: add --allow-empty to git commit in initialize-repo scripts Ensures git init succeeds even on empty repos where nothing has been staged yet. * fix: resolve display names to bundled extensions before catalog download When 'specify extension add "Git Branching Workflow"' is used with a display name instead of the ID, the catalog resolver now runs first to map the name to an ID, then checks bundled extensions again with the resolved ID before falling back to network download. Also noted: EXECUTE_COMMAND_INVOCATION and condition handling match the existing pattern in specify/plan/tasks/implement templates (pre-existing, not introduced by this PR). * fix: handle before_/after_ prefixes in auto-commit message derivation - Strip both before_ and after_ prefixes when deriving command name (fixes misleading 'Auto-commit after before_plan' messages) - Include phase (before/after) in default commit messages - Clarify README config example is an override, not default behavior * fix: use portable grep -qw for word boundary in create-new-feature.sh BSD grep (macOS) doesn't support \b as a word boundary. Replace with grep -qw which is POSIX-portable. * fix: validate hook values, numeric --number, and PS warning routing - Validate each hook value is a dict with a 'command' field during manifest _validate() (prevents crash at install time) - Validate --number is a non-negative integer in bash create-new-feature (clear error instead of cryptic shell arithmetic failure) - Route PowerShell no-git warning to stderr in JSON mode so stdout stays valid JSON --------- Co-authored-by: Manfred Riem <15701806+mnriem@users.noreply.github.com>
1 parent aad6f68 commit 1a9e4d1

33 files changed

Lines changed: 3108 additions & 54 deletions

extensions/EXTENSION-API-REFERENCE.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ defaults: # Optional, default configuration values
108108
#### `hooks`
109109

110110
- **Type**: object
111-
- **Keys**: Event names (e.g., `after_specify`, `after_plan`, `after_tasks`, `after_implement`, `before_commit`)
111+
- **Keys**: Event names (e.g., `after_specify`, `after_plan`, `after_tasks`, `after_implement`, `before_analyze`)
112112
- **Description**: Hooks that execute at lifecycle events
113113
- **Events**: Defined by core spec-kit commands
114114

@@ -559,8 +559,16 @@ Standard events (defined by core):
559559
- `after_tasks` - After task generation
560560
- `before_implement` - Before implementation
561561
- `after_implement` - After implementation
562-
- `before_commit` - Before git commit *(planned - not yet wired into core templates)*
563-
- `after_commit` - After git commit *(planned - not yet wired into core templates)*
562+
- `before_analyze` - Before cross-artifact analysis
563+
- `after_analyze` - After cross-artifact analysis
564+
- `before_checklist` - Before checklist generation
565+
- `after_checklist` - After checklist generation
566+
- `before_clarify` - Before spec clarification
567+
- `after_clarify` - After spec clarification
568+
- `before_constitution` - Before constitution update
569+
- `after_constitution` - After constitution update
570+
- `before_taskstoissues` - Before tasks-to-issues conversion
571+
- `after_taskstoissues` - After tasks-to-issues conversion
564572

565573
### Hook Configuration
566574

extensions/EXTENSION-DEVELOPMENT-GUIDE.md

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -177,9 +177,9 @@ Compatibility requirements.
177177

178178
What the extension provides.
179179

180-
**Required sub-fields**:
180+
**Optional sub-fields**:
181181

182-
- `commands`: Array of command objects (must have at least one)
182+
- `commands`: Array of command objects (at least one command or hook is required)
183183

184184
**Command object**:
185185

@@ -196,12 +196,19 @@ Integration hooks for automatic execution.
196196

197197
Available hook points:
198198

199-
- `after_tasks`: After `/speckit.tasks` completes
200-
- `after_implement`: After `/speckit.implement` completes (future)
199+
- `before_specify` / `after_specify`: Before/after specification generation
200+
- `before_plan` / `after_plan`: Before/after implementation planning
201+
- `before_tasks` / `after_tasks`: Before/after task generation
202+
- `before_implement` / `after_implement`: Before/after implementation
203+
- `before_analyze` / `after_analyze`: Before/after cross-artifact analysis
204+
- `before_checklist` / `after_checklist`: Before/after checklist generation
205+
- `before_clarify` / `after_clarify`: Before/after spec clarification
206+
- `before_constitution` / `after_constitution`: Before/after constitution update
207+
- `before_taskstoissues` / `after_taskstoissues`: Before/after tasks-to-issues conversion
201208

202209
Hook object:
203210

204-
- `command`: Command to execute (must be in `provides.commands`)
211+
- `command`: Command to execute (typically from `provides.commands`, but can reference any registered command)
205212
- `optional`: If true, prompt user before executing
206213
- `prompt`: Prompt text for optional hooks
207214
- `description`: Hook description

extensions/EXTENSION-USER-GUIDE.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -403,8 +403,10 @@ settings:
403403
404404
# Hook configuration
405405
# Available events: before_specify, after_specify, before_plan, after_plan,
406-
# before_tasks, after_tasks, before_implement, after_implement
407-
# Planned (not yet wired into core templates): before_commit, after_commit
406+
# before_tasks, after_tasks, before_implement, after_implement,
407+
# before_analyze, after_analyze, before_checklist, after_checklist,
408+
# before_clarify, after_clarify, before_constitution, after_constitution,
409+
# before_taskstoissues, after_taskstoissues
408410
hooks:
409411
after_tasks:
410412
- extension: jira

extensions/catalog.json

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,23 @@
11
{
22
"schema_version": "1.0",
3-
"updated_at": "2026-03-10T00:00:00Z",
3+
"updated_at": "2026-04-06T00:00:00Z",
44
"catalog_url": "https://raw.githubusercontent.com/github/spec-kit/main/extensions/catalog.json",
55
"extensions": {
6+
"git": {
7+
"name": "Git Branching Workflow",
8+
"id": "git",
9+
"version": "1.0.0",
10+
"description": "Feature branch creation, numbering (sequential/timestamp), validation, and Git remote detection",
11+
"author": "spec-kit-core",
12+
"repository": "https://github.com/github/spec-kit",
13+
"download_url": "https://github.com/github/spec-kit/releases/download/ext-git-v1.0.0/git.zip",
14+
"tags": [
15+
"git",
16+
"branching",
17+
"workflow",
18+
"core"
19+
]
20+
},
621
"selftest": {
722
"name": "Spec Kit Self-Test Utility",
823
"id": "selftest",

extensions/git/README.md

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# Git Branching Workflow Extension
2+
3+
Git repository initialization, feature branch creation, numbering (sequential/timestamp), validation, remote detection, and auto-commit for Spec Kit.
4+
5+
## Overview
6+
7+
This extension provides Git operations as an optional, self-contained module. It manages:
8+
9+
- **Repository initialization** with configurable commit messages
10+
- **Feature branch creation** with sequential (`001-feature-name`) or timestamp (`20260319-143022-feature-name`) numbering
11+
- **Branch validation** to ensure branches follow naming conventions
12+
- **Git remote detection** for GitHub integration (e.g., issue creation)
13+
- **Auto-commit** after core commands (configurable per-command with custom messages)
14+
15+
## Commands
16+
17+
| Command | Description |
18+
|---------|-------------|
19+
| `speckit.git.initialize` | Initialize a Git repository with a configurable commit message |
20+
| `speckit.git.feature` | Create a feature branch with sequential or timestamp numbering |
21+
| `speckit.git.validate` | Validate current branch follows feature branch naming conventions |
22+
| `speckit.git.remote` | Detect Git remote URL for GitHub integration |
23+
| `speckit.git.commit` | Auto-commit changes (configurable per-command enable/disable and messages) |
24+
25+
## Hooks
26+
27+
| Event | Command | Optional | Description |
28+
|-------|---------|----------|-------------|
29+
| `before_constitution` | `speckit.git.initialize` | No | Init git repo before constitution |
30+
| `before_specify` | `speckit.git.feature` | No | Create feature branch before specification |
31+
| `before_clarify` | `speckit.git.commit` | Yes | Commit outstanding changes before clarification |
32+
| `before_plan` | `speckit.git.commit` | Yes | Commit outstanding changes before planning |
33+
| `before_tasks` | `speckit.git.commit` | Yes | Commit outstanding changes before task generation |
34+
| `before_implement` | `speckit.git.commit` | Yes | Commit outstanding changes before implementation |
35+
| `before_checklist` | `speckit.git.commit` | Yes | Commit outstanding changes before checklist |
36+
| `before_analyze` | `speckit.git.commit` | Yes | Commit outstanding changes before analysis |
37+
| `before_taskstoissues` | `speckit.git.commit` | Yes | Commit outstanding changes before issue sync |
38+
| `after_constitution` | `speckit.git.commit` | Yes | Auto-commit after constitution update |
39+
| `after_specify` | `speckit.git.commit` | Yes | Auto-commit after specification |
40+
| `after_clarify` | `speckit.git.commit` | Yes | Auto-commit after clarification |
41+
| `after_plan` | `speckit.git.commit` | Yes | Auto-commit after planning |
42+
| `after_tasks` | `speckit.git.commit` | Yes | Auto-commit after task generation |
43+
| `after_implement` | `speckit.git.commit` | Yes | Auto-commit after implementation |
44+
| `after_checklist` | `speckit.git.commit` | Yes | Auto-commit after checklist |
45+
| `after_analyze` | `speckit.git.commit` | Yes | Auto-commit after analysis |
46+
| `after_taskstoissues` | `speckit.git.commit` | Yes | Auto-commit after issue sync |
47+
48+
## Configuration
49+
50+
Configuration is stored in `.specify/extensions/git/git-config.yml`:
51+
52+
```yaml
53+
# Branch numbering strategy: "sequential" or "timestamp"
54+
branch_numbering: sequential
55+
56+
# Custom commit message for git init
57+
init_commit_message: "[Spec Kit] Initial commit"
58+
59+
# Auto-commit per command (all disabled by default)
60+
# Example: enable auto-commit after specify
61+
auto_commit:
62+
default: false
63+
after_specify:
64+
enabled: true
65+
message: "[Spec Kit] Add specification"
66+
```
67+
68+
## Installation
69+
70+
```bash
71+
# Install the bundled git extension (no network required)
72+
specify extension add git
73+
```
74+
75+
## Disabling
76+
77+
```bash
78+
# Disable the git extension (spec creation continues without branching)
79+
specify extension disable git
80+
81+
# Re-enable it
82+
specify extension enable git
83+
```
84+
85+
## Graceful Degradation
86+
87+
When Git is not installed or the directory is not a Git repository:
88+
- Spec directories are still created under `specs/`
89+
- Branch creation is skipped with a warning
90+
- Branch validation is skipped with a warning
91+
- Remote detection returns empty results
92+
93+
## Scripts
94+
95+
The extension bundles cross-platform scripts:
96+
97+
- `scripts/bash/create-new-feature.sh` — Bash implementation
98+
- `scripts/bash/git-common.sh` — Shared Git utilities (Bash)
99+
- `scripts/powershell/create-new-feature.ps1` — PowerShell implementation
100+
- `scripts/powershell/git-common.ps1` — Shared Git utilities (PowerShell)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
---
2+
description: "Auto-commit changes after a Spec Kit command completes"
3+
---
4+
5+
# Auto-Commit Changes
6+
7+
Automatically stage and commit all changes after a Spec Kit command completes.
8+
9+
## Behavior
10+
11+
This command is invoked as a hook after (or before) core commands. It:
12+
13+
1. Determines the event name from the hook context (e.g., if invoked as an `after_specify` hook, the event is `after_specify`; if `before_plan`, the event is `before_plan`)
14+
2. Checks `.specify/extensions/git/git-config.yml` for the `auto_commit` section
15+
3. Looks up the specific event key to see if auto-commit is enabled
16+
4. Falls back to `auto_commit.default` if no event-specific key exists
17+
5. Uses the per-command `message` if configured, otherwise a default message
18+
6. If enabled and there are uncommitted changes, runs `git add .` + `git commit`
19+
20+
## Execution
21+
22+
Determine the event name from the hook that triggered this command, then run the script:
23+
24+
- **Bash**: `.specify/extensions/git/scripts/bash/auto-commit.sh <event_name>`
25+
- **PowerShell**: `.specify/extensions/git/scripts/powershell/auto-commit.ps1 <event_name>`
26+
27+
Replace `<event_name>` with the actual hook event (e.g., `after_specify`, `before_plan`, `after_implement`).
28+
29+
## Configuration
30+
31+
In `.specify/extensions/git/git-config.yml`:
32+
33+
```yaml
34+
auto_commit:
35+
default: false # Global toggle — set true to enable for all commands
36+
after_specify:
37+
enabled: true # Override per-command
38+
message: "[Spec Kit] Add specification"
39+
after_plan:
40+
enabled: false
41+
message: "[Spec Kit] Add implementation plan"
42+
```
43+
44+
## Graceful Degradation
45+
46+
- If Git is not available or the current directory is not a repository: skips with a warning
47+
- If no config file exists: skips (disabled by default)
48+
- If no changes to commit: skips with a message
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
---
2+
description: "Create a feature branch with sequential or timestamp numbering"
3+
---
4+
5+
# Create Feature Branch
6+
7+
Create a new feature branch for the given specification.
8+
9+
## User Input
10+
11+
```text
12+
$ARGUMENTS
13+
```
14+
15+
You **MUST** consider the user input before proceeding (if not empty).
16+
17+
## Prerequisites
18+
19+
- Verify Git is available by running `git rev-parse --is-inside-work-tree 2>/dev/null`
20+
- If Git is not available, warn the user and skip branch creation (spec directory will still be created)
21+
22+
## Branch Numbering Mode
23+
24+
Determine the branch numbering strategy by checking configuration in this order:
25+
26+
1. Check `.specify/extensions/git/git-config.yml` for `branch_numbering` value
27+
2. Check `.specify/init-options.json` for `branch_numbering` value (backward compatibility)
28+
3. Default to `sequential` if neither exists
29+
30+
## Execution
31+
32+
Generate a concise short name (2-4 words) for the branch:
33+
- Analyze the feature description and extract the most meaningful keywords
34+
- Use action-noun format when possible (e.g., "add-user-auth", "fix-payment-bug")
35+
- Preserve technical terms and acronyms (OAuth2, API, JWT, etc.)
36+
37+
Run the appropriate script based on your platform:
38+
39+
- **Bash**: `.specify/extensions/git/scripts/bash/create-new-feature.sh --json --short-name "<short-name>" "<feature description>"`
40+
- **Bash (timestamp)**: `.specify/extensions/git/scripts/bash/create-new-feature.sh --json --timestamp --short-name "<short-name>" "<feature description>"`
41+
- **PowerShell**: `.specify/extensions/git/scripts/powershell/create-new-feature.ps1 -Json -ShortName "<short-name>" "<feature description>"`
42+
- **PowerShell (timestamp)**: `.specify/extensions/git/scripts/powershell/create-new-feature.ps1 -Json -Timestamp -ShortName "<short-name>" "<feature description>"`
43+
44+
**IMPORTANT**:
45+
- Do NOT pass `--number` — the script determines the correct next number automatically
46+
- Always include the JSON flag (`--json` for Bash, `-Json` for PowerShell) so the output can be parsed reliably
47+
- You must only ever run this script once per feature
48+
- The JSON output will contain BRANCH_NAME and SPEC_FILE paths
49+
50+
If the extension scripts are not found at the `.specify/extensions/git/` path, fall back to:
51+
- **Bash**: `scripts/bash/create-new-feature.sh`
52+
- **PowerShell**: `scripts/powershell/create-new-feature.ps1`
53+
54+
## Graceful Degradation
55+
56+
If Git is not installed or the current directory is not a Git repository:
57+
- The script will still create the spec directory under `specs/`
58+
- A warning will be printed: `[specify] Warning: Git repository not detected; skipped branch creation`
59+
- The workflow continues normally without branch creation
60+
61+
## Output
62+
63+
The script outputs JSON with:
64+
- `BRANCH_NAME`: The created branch name (e.g., `003-user-auth` or `20260319-143022-user-auth`)
65+
- `SPEC_FILE`: Path to the created spec file
66+
- `FEATURE_NUM`: The numeric or timestamp prefix used
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
---
2+
description: "Initialize a Git repository with an initial commit"
3+
---
4+
5+
# Initialize Git Repository
6+
7+
Initialize a Git repository in the current project directory if one does not already exist.
8+
9+
## Execution
10+
11+
Run the appropriate script from the project root:
12+
13+
- **Bash**: `.specify/extensions/git/scripts/bash/initialize-repo.sh`
14+
- **PowerShell**: `.specify/extensions/git/scripts/powershell/initialize-repo.ps1`
15+
16+
If the extension scripts are not found, fall back to:
17+
- **Bash**: `git init && git add . && git commit -m "Initial commit from Specify template"`
18+
- **PowerShell**: `git init; git add .; git commit -m "Initial commit from Specify template"`
19+
20+
The script handles all checks internally:
21+
- Skips if Git is not available
22+
- Skips if already inside a Git repository
23+
- Runs `git init`, `git add .`, and `git commit` with an initial commit message
24+
25+
## Customization
26+
27+
Replace the script to add project-specific Git initialization steps:
28+
- Custom `.gitignore` templates
29+
- Default branch naming (`git config init.defaultBranch`)
30+
- Git LFS setup
31+
- Git hooks installation
32+
- Commit signing configuration
33+
- Git Flow initialization
34+
35+
## Output
36+
37+
On success:
38+
- `✓ Git repository initialized`
39+
40+
## Graceful Degradation
41+
42+
If Git is not installed:
43+
- Warn the user
44+
- Skip repository initialization
45+
- The project continues to function without Git (specs can still be created under `specs/`)
46+
47+
If Git is installed but `git init`, `git add .`, or `git commit` fails:
48+
- Surface the error to the user
49+
- Stop this command rather than continuing with a partially initialized repository

0 commit comments

Comments
 (0)