Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 51 additions & 3 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,20 @@ on:
jobs:
markdownlint:
runs-on: ubuntu-latest
env:
DOC_GLOBS: |
docs/**/*.md
README.md
README.zh-CN.md
CODE_OF_CONDUCT.md
CONTRIBUTING.md
DEVELOPMENT.md
SECURITY.md
SUPPORT.md
spec-driven.md
integrations/*.md
presets/*.md
workflows/*.md
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand All @@ -36,12 +50,46 @@ jobs:
git diff --check refs/checks/push-before HEAD
fi

# Documentation only. Commands, skills, prompt templates, agent
# instructions (AGENTS.md) and .github/ content are inputs to coding
# agents rather than prose, and are deliberately left unlinted so a
# documentation pass never reformats them. Add new documentation
# paths to the DOC_GLOBS list above.
- name: Verify the documentation globs match files
shell: bash
run: |
set -euo pipefail
shopt -s globstar nullglob

# Checked per glob, not on the total: one stale entry among several
# still leaves that part of the documentation unlinted, which is the
# failure #4526 was about.
count=0
empty=()
while IFS= read -r glob; do
[ -z "$glob" ] && continue
matched=0
for path in $glob; do
[ -f "$path" ] && matched=$((matched + 1))
done
# An array, not a string: nullglob is on, so re-expanding an
# unquoted list of unmatched globs would erase it.
[ "$matched" -eq 0 ] && empty+=("$glob")
count=$((count + matched))
done <<< "$DOC_GLOBS"

echo "documentation files matched: $count"
if [ ${#empty[@]} -gt 0 ]; then
for glob in "${empty[@]}"; do
echo "::error::markdownlint glob matches no files: $glob (see #4526)"
done
exit 1
fi

- name: Run markdownlint-cli2
uses: DavidAnson/markdownlint-cli2-action@21c1be1b93ad9ed58fa840aacc3f279cde2a72ff # v24.2.0
with:
globs: |
'**/*.md'
!extensions/**/*.md
globs: ${{ env.DOC_GLOBS }}

shellcheck:
runs-on: ubuntu-latest
Expand Down
8 changes: 4 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -278,21 +278,21 @@ Any change that affects a slash command's behavior requires manually testing tha

Paste this into your PR:

~~~markdown
```markdown
## Manual test results

**Agent**: [e.g., GitHub Copilot in VS Code] | **OS/Shell**: [e.g., macOS/zsh]

| Command tested | Notes |
|----------------|-------|
| `/speckit.command` | |
~~~
```

#### Determining which tests to run

Copy this prompt into your agent. Include the agent's response (selected tests plus a brief explanation of the mapping) in your PR.

~~~text
```text
Read CONTRIBUTING.md, then run `git diff --name-only main` to get my changed files.
For each changed file, determine which slash commands it affects by reading
the command templates in templates/commands/ to understand what each command
Expand Down Expand Up @@ -324,7 +324,7 @@ Number each test sequentially (T1, T2, ...). List prerequisite tests first.

- T1: /speckit.command — (reason)
- T2: /speckit.command — (reason)
~~~
```

## AI contributions in Spec Kit

Expand Down
2 changes: 1 addition & 1 deletion docs/community/friends.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ Community projects that extend, visualize, or build on Spec Kit:

- **[spectatui](https://github.com/tinesoft/spectatui)** — A terminal UI (TUI) dashboard for Spec Kit that lets you track features, manage specifications, integrations, presets, workflows, and extensions, and monitor AI agent workflows. Attach to existing AI sessions or launch new ones from your terminal. Keyboard and mouse support. Light/dark theme support. Customizable and performance-oriented. Requires the `specify` CLI in your PATH.

- **[spec-kit-copilot](https://github.com/github/spec-kit-copilot)** — _First-party GitHub project._ A GitHub Copilot **skills plugin** that exposes the Spec Kit `specify` CLI to the Copilot agent in both the Copilot CLI and the GitHub Copilot app. It provides a focused skill per `specify` command group — setup, init, check, extensions, presets, bundles, workflows, workflow steps, and self-upgrade — so you can navigate and drive the entire Spec Kit ecosystem through natural language, letting Copilot decide when and how to run the right `specify` commands on your behalf.
- **[spec-kit-copilot](https://github.com/github/spec-kit-copilot)** — *First-party GitHub project.* A GitHub Copilot **skills plugin** that exposes the Spec Kit `specify` CLI to the Copilot agent in both the Copilot CLI and the GitHub Copilot app. It provides a focused skill per `specify` command group — setup, init, check, extensions, presets, bundles, workflows, workflow steps, and self-upgrade — so you can navigate and drive the entire Spec Kit ecosystem through natural language, letting Copilot decide when and how to run the right `specify` commands on your behalf.
3 changes: 3 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ Adding Spec Kit to an established codebase? Start with the

<div class="pillar-card">

<!-- Pillar headings stay at h3: docs/template/public/main.css styles
`.pillar-card h3`, including the per-card accent colors. -->
<!-- markdownlint-disable-next-line MD001 -->
### Spec-driven by default

The core SDD process ships ready to use: **Specify → Plan → Tasks → Implement → Converge**.
Expand Down
4 changes: 2 additions & 2 deletions docs/install/air-gapped.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ specify init my-project --integration copilot
```

> **Note:** Python 3.11+ is required.

> **Windows note:** Offline scaffolding requires PowerShell 7+ (`pwsh`), not Windows PowerShell 5.x (`powershell.exe`). Install from https://aka.ms/powershell.
>
> **Windows note:** Offline scaffolding requires PowerShell 7+ (`pwsh`), not Windows PowerShell 5.x (`powershell.exe`). Install from <https://aka.ms/powershell>.

## Git Credential Manager on Linux

Expand Down
2 changes: 1 addition & 1 deletion docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
- AI coding agent: [Claude Code](https://www.anthropic.com/claude-code), [GitHub Copilot](https://code.visualstudio.com/), [CodeBuddy CLI](https://www.codebuddy.cn/docs/cli/installation), [Gemini CLI](https://github.com/google-gemini/gemini-cli), [Pi Coding Agent](https://pi.dev), or [Oh My Pi](https://www.npmjs.com/package/@oh-my-pi/pi-coding-agent)
- [uv](https://docs.astral.sh/uv/) for package management (recommended) or [pipx](https://pipx.pypa.io/) for persistent installation
- [Python 3.11+](https://www.python.org/downloads/)
- [Git](https://git-scm.com/downloads) _(optional — required only when the git extension is enabled)_
- [Git](https://git-scm.com/downloads) *(optional — required only when the git extension is enabled)*

## Installation

Expand Down
1 change: 1 addition & 0 deletions docs/reference/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Create `~/.specify/auth.json` to enable authentication:
```

> **Security:** Restrict the file to owner-only access:
>
> ```bash
> chmod 600 ~/.specify/auth.json
> ```
Expand Down
4 changes: 2 additions & 2 deletions docs/reference/core.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ specify init my-project --integration copilot --preset compliance
| `SPECIFY_FEATURE` | Explicitly override the active feature **label** (e.g. `001-photo-albums`) — the identifier the core helpers report as the current feature/branch (`get_current_branch` in Bash, `Get-CurrentBranch` in PowerShell). Those helpers never inspect Git: when the variable is set they return it verbatim, and when it is unset they return an empty string. The basename fallback happens later — `get_feature_paths` / `Get-FeaturePathsEnv` substitute the resolved feature directory's basename so the reported label is still usable — so calling the named helpers directly does **not** give you that fallback. You set it yourself: the Bash and Python feature scripts only **print** a commented `export SPECIFY_FEATURE=…` / `$env:SPECIFY_FEATURE = …` hint for you to run, because a child process cannot change its parent's environment, and `/speckit.specify` persists `feature_directory` to `.specify/feature.json` instead of setting this variable. (The PowerShell feature scripts do assign `$env:SPECIFY_FEATURE`, but that only reaches you when the script runs inside your current PowerShell session.) It does **not** locate the feature directory: with only `SPECIFY_FEATURE` set, `get_feature_paths` fails with *"Feature directory not found. Set `SPECIFY_FEATURE_DIRECTORY` or run the specify command to create `.specify/feature.json`."* Use `SPECIFY_FEATURE_DIRECTORY` (above) or `.specify/feature.json` to select the directory. |

> **Two resolution axes.** `SPECIFY_INIT_DIR` selects the **project** (which directory contains `.specify/`); `SPECIFY_FEATURE_DIRECTORY` / `.specify/feature.json` select the **feature** within that project. They are independent — project first, then feature.

>
> **Version control.** `specify init` scaffolds a managed `.specify/.gitignore` that excludes machine-local state — `feature.json` (the current-feature pointer, rewritten on every feature switch) and per-machine extension `extensions/*/local-config.yml` overrides — while leaving everything else under `.specify/` (constitution, templates, scripts, extension config) shareable so teams stay aligned. Like the rest of `.specify/`'s shared scripts and templates, the file is tracked in the shared-infrastructure manifest: your edits are preserved on re-init and `specify init --here --force` restores the managed content. It is intentionally left in place by `specify integration uninstall`, which only removes the uninstalled agent's own files.

>
> **Symlinked project roots.** `SPECIFY_INIT_DIR` relocates *where* the project is, not *how* a command treats symlinks: each command keeps its existing cwd-path stance. Commands that traverse and write project files through broad input paths (`bundle`, `workflow run <file>`) refuse a symlinked `.specify/` to preserve write confinement. Other project-scoped commands keep their existing behavior when `SPECIFY_INIT_DIR` points at a project root, which may include following a symlinked `.specify/`.

## Check Installed Tools
Expand Down
5 changes: 5 additions & 0 deletions docs/reference/extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,12 @@ Catalogs come in two kinds, and the distinction is a **security boundary**, not
> **Do not flip a discovery-only catalog to `install_allowed`.** That defeats the entire point of separating discovery from installation. There are two correct ways to install something you found via `community`:
>
> 1. **Install a single vetted extension directly** with `--from` (no catalog authoring needed). Get the candidate archive URL from `specify extension info <name>` — for a discovery-only entry it prints a "Candidate archive" URL. Review that release archive, then install it:
>
> ```bash
> specify extension info <name> # shows the candidate archive URL
> specify extension add <name> --from <archive-url>
> ```
>
> Treat the URL as untrusted until you have vetted it — it comes from an unvetted catalog.
> 2. **Curate your own catalog** you control and vet, and mark *that* catalog `install_allowed: true` — for when you want a governed, reusable install source (e.g. for an org).

Expand Down Expand Up @@ -208,13 +210,15 @@ To set up configuration for a newly installed extension, copy the template:
cp .specify/extensions/<ext>/<ext>-config.template.yml \
.specify/extensions/<ext>/<ext>-config.yml
```

## Project Extension and Hook Configuration

Spec Kit stores project-level extension registration and hook configuration in:

```text
.specify/extensions.yml
```

The file contains installed extensions, global settings, and hooks that are surfaced before or after Spec Kit commands.

```yaml
Expand Down Expand Up @@ -262,6 +266,7 @@ Each hook entry supports the following fields:
| `prompt` | Message shown when asking whether to run an optional hook. |
| `description` | Human-readable explanation of what the hook does. |
| `condition` | Optional expression evaluated by `HookExecutor` (using `config.<path>` or `env.<VAR>` with `is set`, `==`, or `!=`). Current command templates do not evaluate conditions and skip hooks with a non-empty condition. |

Hook event names identify when a hook is invoked. They generally use `before_<command>` or `after_<command>`, such as `before_implement`, `after_implement`, `before_tasks`, and `after_tasks`.

Extension manifests reject invalid hook priorities during installation. For existing `.specify/extensions.yml` entries, `HookExecutor.get_hooks_for_event()` sorts with `normalize_priority()`: missing values, booleans, non-numeric values rejected by `int()`, and values less than `1` fall back to `10`; numeric strings and finite floats are coerced with `int()`, while non-finite floats are unsupported and may fail instead of falling back.
Expand Down
1 change: 1 addition & 0 deletions docs/reference/workflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@ When an installed workflow is refreshed or reinstalled, project overlays in `.sp
- An overlay that targets a step id that does not exist in the base workflow will raise a validation error when the workflow is resolved.
- Overlays cannot target steps added by other overlays.
- Overlays cannot add new inputs or change the input schema of the base workflow.

## Update Workflows

```bash
Expand Down
32 changes: 16 additions & 16 deletions integrations/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,22 +94,22 @@ provides:
1. **Fork** the [spec-kit repository](https://github.com/github/spec-kit)
2. **Add your entry** under the `integrations` key in `integrations/catalog.community.json`:

```json
{
"schema_version": "1.0",
"integrations": {
"my-agent": {
"id": "my-agent",
"name": "My Agent",
"version": "1.0.0",
"description": "Integration for My Agent",
"author": "your-name",
"repository": "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/your-name/speckit-my-agent",
"tags": ["cli"]
}
}
}
```
```json
{
"schema_version": "1.0",
"integrations": {
"my-agent": {
"id": "my-agent",
"name": "My Agent",
"version": "1.0.0",
"description": "Integration for My Agent",
"author": "your-name",
"repository": "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/your-name/speckit-my-agent",
"tags": ["cli"]
}
}
}
```

3. **Open a pull request** with:
- Your catalog entry
Expand Down
6 changes: 4 additions & 2 deletions presets/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ flowchart TD
When multiple presets are installed, they're sorted by their `priority` field (lower number = higher precedence). This is set via `--priority` on `specify preset add`.

The resolution is implemented three times to ensure consistency:

- **Python**: `PresetResolver` in `src/specify_cli/presets.py`
- **Bash**: `resolve_template()` in `scripts/bash/common.sh`
- **PowerShell**: `Resolve-Template` in `scripts/powershell/common.ps1`
Expand All @@ -55,6 +56,7 @@ Templates, commands, and scripts support a `strategy` field that controls how a
Composition is recursive — multiple composing presets chain. The `PresetResolver.resolve_content()` method walks the full priority stack bottom-up and applies each layer's strategy.

Content resolution functions for composition:

- **Python**: `PresetResolver.resolve_content()` in `src/specify_cli/presets.py` (templates, commands, and scripts)
- **Bash**: `resolve_template_content()` in `scripts/bash/common.sh` (templates only; command/script composition is handled by the Python resolver)
- **PowerShell**: `Resolve-TemplateContent` in `scripts/powershell/common.ps1` (templates only; command/script composition is handled by the Python resolver)
Expand Down Expand Up @@ -146,7 +148,7 @@ Catalogs are fetched with a 1-hour cache (per-URL, SHA256-hashed cache files). E

## Repository Layout

```
```text
presets/
├── ARCHITECTURE.md # This file
├── PUBLISHING.md # Guide for submitting presets to the catalog
Expand Down Expand Up @@ -176,7 +178,7 @@ presets/

## Module Structure

```
```text
src/specify_cli/
├── agents.py # CommandRegistrar — shared infrastructure for writing
│ # command files to agent directories
Expand Down
2 changes: 1 addition & 1 deletion presets/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ See [scaffold/](scaffold/) for a scaffold you can copy to create your own preset
| `SPECKIT_PRESET_CATALOG_URL` | Override the full catalog stack with a single URL (replaces all defaults) | Built-in default stack |
| `GH_TOKEN` / `GITHUB_TOKEN` | GitHub token for authenticated requests to GitHub-hosted URLs (`raw.githubusercontent.com`, `github.com`, `api.github.com`, `codeload.github.com`). Required when your catalog JSON or preset ZIPs are hosted in a private GitHub repository. | None |

#### Example: Using a private GitHub-hosted catalog
### Example: Using a private GitHub-hosted catalog

```bash
# Authenticate with a token (gh CLI, PAT, or GITHUB_TOKEN in CI)
Expand Down
2 changes: 1 addition & 1 deletion workflows/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ When `specify workflow add <id>` installs from catalog, it downloads the workflo

## Module Structure

```
```text
src/specify_cli/
├── workflows/
│ ├── __init__.py # STEP_REGISTRY + _register_builtin_steps()
Expand Down
2 changes: 1 addition & 1 deletion workflows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -532,7 +532,7 @@ specify workflow catalog remove <index>

## Repository Layout

```
```text
workflows/
├── ARCHITECTURE.md # Internal architecture documentation
├── PUBLISHING.md # Guide for submitting workflows to the catalog
Expand Down