diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index de4eb9a30b..6b11a1d09a 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -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 @@ -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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4f0d0123e1..cac9fa8597 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -278,7 +278,7 @@ 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] @@ -286,13 +286,13 @@ Paste this into your PR: | 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 @@ -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 diff --git a/docs/community/friends.md b/docs/community/friends.md index 2a7fdad5c1..1ff68c63e0 100644 --- a/docs/community/friends.md +++ b/docs/community/friends.md @@ -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. diff --git a/docs/index.md b/docs/index.md index d7f4ca97ee..8ed2b1d41c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -36,6 +36,9 @@ Adding Spec Kit to an established codebase? Start with the
+ + ### Spec-driven by default The core SDD process ships ready to use: **Specify → Plan → Tasks → Implement → Converge**. diff --git a/docs/install/air-gapped.md b/docs/install/air-gapped.md index bf2a46380e..b0e20ec0ca 100644 --- a/docs/install/air-gapped.md +++ b/docs/install/air-gapped.md @@ -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 . ## Git Credential Manager on Linux diff --git a/docs/installation.md b/docs/installation.md index a8a68fc992..2fcb1577ee 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -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 diff --git a/docs/reference/authentication.md b/docs/reference/authentication.md index 059052cd8f..de369c980b 100644 --- a/docs/reference/authentication.md +++ b/docs/reference/authentication.md @@ -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 > ``` diff --git a/docs/reference/core.md b/docs/reference/core.md index b70aebe236..0842b1bfda 100644 --- a/docs/reference/core.md +++ b/docs/reference/core.md @@ -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 `) 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 diff --git a/docs/reference/extensions.md b/docs/reference/extensions.md index 22357ccea0..3c309b58a8 100644 --- a/docs/reference/extensions.md +++ b/docs/reference/extensions.md @@ -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 ` — for a discovery-only entry it prints a "Candidate archive" URL. Review that release archive, then install it: +> > ```bash > specify extension info # shows the candidate archive URL > specify extension add --from > ``` +> > 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). @@ -208,6 +210,7 @@ To set up configuration for a newly installed extension, copy the template: cp .specify/extensions//-config.template.yml \ .specify/extensions//-config.yml ``` + ## Project Extension and Hook Configuration Spec Kit stores project-level extension registration and hook configuration in: @@ -215,6 +218,7 @@ 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 @@ -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.` or `env.` 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_` or `after_`, 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. diff --git a/docs/reference/workflows.md b/docs/reference/workflows.md index a547a10e42..ad2c1965a3 100644 --- a/docs/reference/workflows.md +++ b/docs/reference/workflows.md @@ -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 diff --git a/integrations/CONTRIBUTING.md b/integrations/CONTRIBUTING.md index 77a50d4d98..c441ecc068 100644 --- a/integrations/CONTRIBUTING.md +++ b/integrations/CONTRIBUTING.md @@ -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": "https://github.com/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": "https://github.com/your-name/speckit-my-agent", + "tags": ["cli"] + } + } + } + ``` 3. **Open a pull request** with: - Your catalog entry diff --git a/presets/ARCHITECTURE.md b/presets/ARCHITECTURE.md index 2ef78add27..db540f7c72 100644 --- a/presets/ARCHITECTURE.md +++ b/presets/ARCHITECTURE.md @@ -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` @@ -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) @@ -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 @@ -176,7 +178,7 @@ presets/ ## Module Structure -``` +```text src/specify_cli/ ├── agents.py # CommandRegistrar — shared infrastructure for writing │ # command files to agent directories diff --git a/presets/README.md b/presets/README.md index 539da08786..2559c3d0cb 100644 --- a/presets/README.md +++ b/presets/README.md @@ -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) diff --git a/workflows/ARCHITECTURE.md b/workflows/ARCHITECTURE.md index 680cfc4237..db5d48f52f 100644 --- a/workflows/ARCHITECTURE.md +++ b/workflows/ARCHITECTURE.md @@ -191,7 +191,7 @@ When `specify workflow add ` installs from catalog, it downloads the workflo ## Module Structure -``` +```text src/specify_cli/ ├── workflows/ │ ├── __init__.py # STEP_REGISTRY + _register_builtin_steps() diff --git a/workflows/README.md b/workflows/README.md index 2c1a9f2bb7..1940420a45 100644 --- a/workflows/README.md +++ b/workflows/README.md @@ -532,7 +532,7 @@ specify workflow catalog remove ## Repository Layout -``` +```text workflows/ ├── ARCHITECTURE.md # Internal architecture documentation ├── PUBLISHING.md # Guide for submitting workflows to the catalog