From f7e2cc8be5ead1f0041f291c5b51d06ae62fdeab Mon Sep 17 00:00:00 2001 From: SurefireStudios <123013554+SurefireStudios@users.noreply.github.com> Date: Mon, 14 Sep 2026 11:18:30 -0700 Subject: [PATCH 1/3] fix(ci): make the markdownlint job lint files again (#4526) The globs were written inside a YAML block scalar, so the quotes around `'**/*.md'` were passed through to markdownlint-cli2 verbatim. It looked for paths beginning with a literal quote, matched nothing, and exited 0. The job has reported success without reading a file since it was added. Unquoting the glob alone turns the job red: it then finds 116 files with 277 violations. So this also clears every one of them. Most were mechanical (blank lines around fences, lists and headings) and came from markdownlint's own --fix. Three groups needed judgment: - Identifiers that markdown reads as emphasis. `__init__.py`, `__SPECKIT_COMMAND_*__` and friends were being rendered as bold in CHANGELOG.md. Running --fix over them rewrites the text itself (`**init**.py`), so they are wrapped in code spans instead, matching how the same tokens are already written elsewhere in the repo. - Fenced blocks without a language. Tagged from their actual content: `markdown` for the blocks that are markdown output, `text` for directory trees, commit messages and log excerpts. - The pillar headings on the docs landing page. They must stay at h3 because main.css styles `.pillar-card h3`, so MD001 is suppressed there with a comment saying why. It is the only suppression added. No prose changed. Verified against markdownlint-cli2 0.23.2, the version the pinned action actually runs. Assisted-by: Claude (model: claude-opus-5, autonomous) --- .../skills/add-community-extension/SKILL.md | 4 +- .github/workflows/RELEASE-PROCESS.md | 14 ++++- .github/workflows/add-community-extension.md | 18 +++++-- .github/workflows/add-community-preset.md | 18 +++++-- .github/workflows/lint.yml | 2 +- AGENTS.md | 2 +- CHANGELOG.md | 54 +++++++++---------- CONTRIBUTING.md | 8 +-- docs/community/friends.md | 2 +- docs/index.md | 5 +- docs/install/air-gapped.md | 4 +- docs/installation.md | 2 +- docs/reference/authentication.md | 1 + docs/reference/core.md | 4 +- docs/reference/extensions.md | 5 ++ docs/reference/workflows.md | 1 + integrations/CONTRIBUTING.md | 32 +++++------ presets/ARCHITECTURE.md | 6 ++- presets/README.md | 2 +- presets/lean/commands/speckit.specify.md | 1 + presets/scaffold/README.md | 1 + templates/commands/analyze.md | 20 +++++-- templates/commands/checklist.md | 18 +++++-- templates/commands/clarify.md | 18 +++++-- templates/commands/constitution.md | 18 +++++-- templates/commands/converge.md | 2 + templates/commands/implement.md | 17 ++++-- templates/commands/plan.md | 17 ++++-- templates/commands/specify.md | 20 +++++-- templates/commands/tasks.md | 18 +++++-- templates/commands/taskstoissues.md | 18 +++++-- workflows/ARCHITECTURE.md | 2 +- workflows/README.md | 2 +- 33 files changed, 250 insertions(+), 106 deletions(-) diff --git a/.github/skills/add-community-extension/SKILL.md b/.github/skills/add-community-extension/SKILL.md index 179c11b3e2..78dcf2ae5c 100644 --- a/.github/skills/add-community-extension/SKILL.md +++ b/.github/skills/add-community-extension/SKILL.md @@ -19,6 +19,7 @@ Process an extension submission issue and add or update it in the community cata ### 1. Fetch the submission issue Read the GitHub issue to extract all metadata: + - Extension ID, name, version, description, author - Repository URL, download URL, homepage, documentation, changelog - License, required spec-kit version, optional tool dependencies @@ -114,7 +115,7 @@ python3 -c "import json; json.load(open('extensions/catalog.community.json')); p Determine the category and effect from the extension's behavior: -``` +```markdown | | | `` | | []() | ``` @@ -158,6 +159,7 @@ git push origin ``` Then create a PR to `upstream` (`github/spec-kit`) with: + - **Title:** `Add extension to community catalog` (or `Update extension to v`) - **Body:** Include validation summary, `Closes #`, and `cc @` - **Head:** `:` diff --git a/.github/workflows/RELEASE-PROCESS.md b/.github/workflows/RELEASE-PROCESS.md index 18fe40e858..867e510650 100644 --- a/.github/workflows/RELEASE-PROCESS.md +++ b/.github/workflows/RELEASE-PROCESS.md @@ -20,6 +20,7 @@ This separation ensures that git tags always point to commits with the correct v The CHANGELOG is **automatically generated** from your git commit messages: 1. **During Development**: Write clear, descriptive commit messages: + ```bash git commit -m "feat: Add new authentication feature" git commit -m "fix: Resolve timeout issue in API client (#123)" @@ -35,13 +36,15 @@ The CHANGELOG is **automatically generated** from your git commit messages: ### Commit Message Best Practices Good commit messages make good changelogs: + - **Be descriptive**: "Add user authentication" not "Update files" - **Reference issues/PRs**: Include `(#123)` for automated linking - **Use conventional commits** (optional): `feat:`, `fix:`, `docs:`, `chore:` - **Keep it concise**: One line is ideal, details go in commit body **Example commits that become good changelog entries:** -``` + +```text fix: prepend YAML frontmatter to Cursor .mdc files (#1699) feat: add generic agent support with customizable command directories (#1639) docs: document dual-catalog system for extensions (#1689) @@ -57,6 +60,7 @@ docs: document dual-catalog system for extensions (#1689) 4. Click **Run workflow** The workflow will: + - Auto-increment the patch version (e.g., `0.1.10` → `0.1.11`) - Update `pyproject.toml` - Update `CHANGELOG.md` by adding a new section for the release based on commits since the last tag @@ -73,6 +77,7 @@ The workflow will: 4. Click **Run workflow** The workflow will: + - Use your specified version - Update `pyproject.toml` - Update `CHANGELOG.md` by adding a new section for the release based on commits since the last tag @@ -105,6 +110,7 @@ Once the release trigger workflow completes: **Permissions Required**: `contents: write` **Steps**: + 1. Checkout repository 2. Determine version (manual or auto-increment) 3. Check if tag already exists (prevents duplicates) @@ -124,6 +130,7 @@ Once the release trigger workflow completes: **Permissions Required**: `contents: write` **Steps**: + 1. Checkout repository at tag 2. Extract version from tag name 3. Check if release already exists @@ -155,6 +162,7 @@ Once the release trigger workflow completes: ### No Commits Since Last Release If you run the release trigger workflow when there are no new commits since the last tag: + - The workflow will still succeed - The CHANGELOG will show "- Initial release" if it's the first release - Or it will be empty if there are no commits @@ -165,12 +173,14 @@ If you run the release trigger workflow when there are no new commits since the ### Tag Already Exists If you see "Error: Tag vX.Y.Z already exists!", you need to: + - Choose a different version number, or - Delete the existing tag if it was created in error ### Release Workflow Didn't Trigger Check that: + - The release trigger workflow completed successfully - The tag was pushed (check repository tags) - The release workflow is enabled in Actions settings @@ -178,12 +188,14 @@ Check that: ### Version Mismatch If `pyproject.toml` doesn't match the latest tag: + - Run the release trigger workflow to sync versions - Or manually update `pyproject.toml` and push changes before running the release trigger ## Legacy Behavior (Pre-v0.1.10) Before this change, the release workflow: + - Created tags automatically on main branch pushes - Updated `pyproject.toml` AFTER creating the tag - Resulted in tags pointing to commits with outdated versions diff --git a/.github/workflows/add-community-extension.md b/.github/workflows/add-community-extension.md index c59f20ddac..d3ef9ecb2b 100644 --- a/.github/workflows/add-community-extension.md +++ b/.github/workflows/add-community-extension.md @@ -99,19 +99,23 @@ Run **all** of the following validation checks. Collect all results before deciding pass/fail: ### 2a. Extension ID format + - Must match regex: `^[a-z][a-z0-9-]*$` - Must be lowercase with hyphens only ### 2b. Version format + - Must follow semver: `X.Y.Z` (digits only, no `v` prefix) ### 2c. Repository validation + - Fetch the repository URL — confirm it exists and is publicly accessible - Confirm the repository contains an `extension.yml` file - Confirm the repository contains a `README.md` file - Confirm the repository contains a `LICENSE` file ### 2d. Release and download URL validation + - The download URL MUST belong to the submitted repository (`https://github.com///...` with the same `/` as the Repository URL). Reject URLs for any other GitHub repository. @@ -136,18 +140,21 @@ deciding pass/fail: - Verify a GitHub release exists for that tag. ### 2e. Submission checklists + - Confirm that all required checkboxes in the Testing Checklist and Submission Requirements sections are checked (`[x]`) ### Validation outcome If **any** validation fails: + 1. Add a comment on the issue listing each failed check with a clear explanation of what's wrong and how to fix it 2. Add the `validation-failed` label 3. **Stop — do not proceed further** If all validations pass: + 1. Add the `validation-passed` label 2. Continue to Step 3 @@ -234,11 +241,12 @@ Extensions table. Insert a new row in **alphabetical order by extension name**: -``` +```markdown | | | `` | | []() | ``` Determine the category from the extension's behavior: + - `docs` — reads, validates, or generates spec artifacts - `code` — reviews, validates, or modifies source code - `process` — orchestrates workflow across phases @@ -246,6 +254,7 @@ Determine the category from the extension's behavior: - `visibility` — reports on project health or progress Determine the effect: + - `Read-only` — produces reports only - `Read+Write` — modifies project files @@ -263,7 +272,8 @@ Create a pull request with the changes. Use this branch naming convention: ### Commit message For a new extension: -``` + +```text Add extension to community catalog Add extension submitted by @ to: @@ -274,7 +284,8 @@ Closes # ``` For an update: -``` + +```text Update extension to v Update extension submitted by @: @@ -287,6 +298,7 @@ Closes # ### PR description Include: + - A summary of what changed - Validation results (all checks passed) - `Closes #${{ github.event.issue.number }}` diff --git a/.github/workflows/add-community-preset.md b/.github/workflows/add-community-preset.md index f93dbef0f6..2b5f262c34 100644 --- a/.github/workflows/add-community-preset.md +++ b/.github/workflows/add-community-preset.md @@ -97,13 +97,16 @@ Run **all** of the following validation checks. Collect all results before deciding pass/fail: ### 2a. Preset ID format + - Must match regex: `^[a-z][a-z0-9-]*$` - Must be lowercase with hyphens only ### 2b. Version format + - Must follow semver: `X.Y.Z` (digits only, no `v` prefix) ### 2c. Repository validation + - Fetch the repository URL — confirm it exists and is publicly accessible - Confirm the repository contains a `preset.yml` file - Confirm the repository contains a `LICENSE` file @@ -163,6 +166,7 @@ preset** — not just any file named `README.md`, and not a product/framework pi `specify preset add ...` command for this preset; otherwise it fails check 2d above. ### 2e. Release and download URL validation + - The download URL MUST belong to the submitted repository (`https://github.com///...` with the same `/` as the Repository URL). Reject URLs for any other GitHub repository. @@ -187,18 +191,21 @@ preset** — not just any file named `README.md`, and not a product/framework pi - Verify a GitHub release exists for that tag. ### 2f. Submission checklists + - Confirm that all required checkboxes in the Testing Checklist and Submission Requirements sections are checked (`[x]`) ### Validation outcome If **any** validation fails: + 1. Add a comment on the issue listing each failed check with a clear explanation of what's wrong and how to fix it 2. Add the `validation-failed` label 3. **Stop — do not proceed further** If all validations pass: + 1. Add the `validation-passed` label 2. Continue to Step 3 @@ -267,6 +274,7 @@ Replace only the changed fields (typically `version`, `download_url`, ### Counting templates and commands Parse the "Templates Provided" and "Commands Provided" issue fields: + - Count the number of list items (lines starting with `-`) - If the field says "None", the count is 0 @@ -292,11 +300,12 @@ Presets table. Insert a new row in **alphabetical order by preset name**: -``` +```markdown | | | templates, commands | | []() | ``` For the Requires column: + - Use `—` if no extensions are required - List required extension names if any (e.g., `AIDE extension`) @@ -316,7 +325,8 @@ Create a pull request with the changes. Use this branch naming convention: ### Commit message For a new preset: -``` + +```text Add preset to community catalog Add preset submitted by @ to: @@ -327,7 +337,8 @@ Closes # ``` For an update: -``` + +```text Update preset to v Update preset submitted by @: @@ -340,6 +351,7 @@ Closes # ### PR description Include: + - A summary of what changed - Validation results (all checks passed) - `Closes #${{ github.event.issue.number }}` diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index de4eb9a30b..06d19182d8 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -40,7 +40,7 @@ jobs: uses: DavidAnson/markdownlint-cli2-action@21c1be1b93ad9ed58fa840aacc3f279cde2a72ff # v24.2.0 with: globs: | - '**/*.md' + **/*.md !extensions/**/*.md shellcheck: diff --git a/AGENTS.md b/AGENTS.md index 9ef3d6c851..c8c4f9db85 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -513,7 +513,7 @@ Disclosure is **continuous**, not a one-time event. A single AI-disclosure parag - **Every commit you author must carry an `Assisted-by:` trailer** identifying the agent and whether it acted autonomously or under direct human supervision, for example: - ``` + ```text Assisted-by: GitHub Copilot (model: , autonomous) ``` diff --git a/CHANGELOG.md b/CHANGELOG.md index 54f581ab04..4961d6113a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -462,7 +462,7 @@ - fix(agent-context): apply default markers when config markers are blank (bash) (#3736) - fix: escape Rich markup in catalog list output (#3738) - fix(workflows): guard non-mapping 'workflow:' block in WorkflowDefinition (#3694) -- fix(bundler): reject unsupported schema_version in _merge_config (align readers) (#3711) +- fix(bundler): reject unsupported schema_version in `_merge_config` (align readers) (#3711) - Update Linear Weave extension to v1.0.1 (#3762) - Add Intake Sequencing Governance preset to community catalog (#3761) - Update Quality Gates (Enforcement Layer) extension to v0.3.3 (#3760) @@ -577,7 +577,7 @@ ### Changed - fix(integrations): escape Rich markup in --integration-options error messages (#3458) -- docs: document __SPECKIT_COMMAND_ token for portable cross-command references (#3503) +- docs: document `__SPECKIT_COMMAND_` token for portable cross-command references (#3503) - [preset] Add Parallel Autonomous Run Governance preset to community catalog (#3614) - docs(workflows): fix stale FanOutStep docstring claiming sequential-only execution (#3639) - [bundle] Add SicarioSpec Security & Governance Bundle to community catalog (#3636) @@ -1154,7 +1154,7 @@ - docs: document spec persistence models (#2856) - chore(catalog): bump Linear Integration to v0.3.0 (repo renamed to spec-kit-linear-sync) (#2893) - chore: update DocGuard extension to v0.25.0 (#2707) -- chore: remove unused open_github_url/_StripAuthOnRedirect from _github_http.py (#2883) +- chore: remove unused `open_github_url/_StripAuthOnRedirect` from `_github_http.py` (#2883) - fix(catalogs): validate extension and preset catalog payload shape (#2621) - feat(integration): add status reporting (#2674) - chore: release 0.10.0, begin 0.10.1.dev0 development (#2904) @@ -1291,7 +1291,7 @@ - docs: update landing page stats and branch naming convention (#2727) - feat(workflows): expose {{ context.run_id }} template variable (#2664) -- fix: resolve __SPECKIT_COMMAND_*__ refs in preset skill rendering (#2717) (#2718) +- fix: resolve `__SPECKIT_COMMAND_*__` refs in preset skill rendering (#2717) (#2718) - Add Workflow Preset to community catalog (#2725) - fix: paths-only skips branch validation, setup-plan preserves existing plan (#2672) - docs: fix broken pipx homepage URLs to point to pipx.pypa.io (#2670) @@ -1351,7 +1351,7 @@ - Add Team Assign extension to community catalog (#2642) - refactor: migrate extension catalog stack parsing to shared base (#2576) - Update Architecture Workflow extension to v1.1.0 (#2588) -- fix(workflow): support integration: auto to follow project's initialized AI (#2421) +- fix(workflow): support integration: auto to follow project's initialized AI (#2421) - Add Superpowers Implementation Bridge extension to community catalog (#2586) - Add Interactive HTML Preview extension to community catalog (#2585) - chore: release 0.8.11, begin 0.8.12.dev0 development (#2584) @@ -1361,7 +1361,7 @@ ### Changed -- refactor: extract _version.py from __init__.py (PR-3/8) (#2550) +- refactor: extract `_version.py` from `__init__.py` (PR-3/8) (#2550) - Add Time Machine extension to community catalog (#2580) - fix(powershell): ensure UTF-8 templates are written without BOM (#2280) - docs: document high-assurance spec workflow (#2518) @@ -1381,9 +1381,9 @@ - Add Agent Governance extension to community catalog (#2559) - Add Reqnroll BDD extension to community catalog (#2545) - fix(cli): harden extension registration and discovery workflows (#2499) -- refactor: extract _assets.py and _utils.py from __init__.py (PR-2/8) (#2543) +- refactor: extract `_assets.py` and `_utils.py` from `__init__.py` (PR-2/8) (#2543) - fix(opencode): use commands/ directory (plural) to match OpenCode docs (#2453) -- refactor: extract _console.py from __init__.py (PR-1/8) (#2474) +- refactor: extract `_console.py` from `__init__.py` (PR-1/8) (#2474) - Fix constitution reference in README (#2491) - chore: release 0.8.9, begin 0.8.10.dev0 development (#2532) @@ -2167,15 +2167,15 @@ - Update CHANGELOG.md - Update agent configuration - Update scripts/powershell/create-new-feature.ps1 -- Update src/specify_cli/__init__.py +- Update `src/specify_cli/__init__.py` - Create create-release-packages.ps1 - Script changes - Update taskstoissues.md - Create taskstoissues.md -- Update src/specify_cli/__init__.py +- Update `src/specify_cli/__init__.py` - Update CONTRIBUTING.md - Potential fix for code scanning alert no. 3: Workflow does not contain permissions -- Update src/specify_cli/__init__.py +- Update `src/specify_cli/__init__.py` - Update CHANGELOG.md - Fixes #970 - Fixes #975 @@ -2358,13 +2358,13 @@ ### Changed -- Update __init__.py +- Update `__init__.py` - Consolidate Cursor naming - Update CHANGELOG.md - Git errors are now highlighted. -- Update __init__.py +- Update `__init__.py` - Refactor agent configuration -- Update src/specify_cli/__init__.py +- Update `src/specify_cli/__init__.py` - Update scripts/powershell/update-agent-context.ps1 - Update AGENTS.md - Update templates/commands/implement.md @@ -2428,10 +2428,10 @@ - Update URLs to Contributing and Support Guides in Docs - fix: add UTF-8 encoding to file read/write operations in update-agent-context.ps1 -- Update __init__.py -- Update src/specify_cli/__init__.py +- Update `__init__.py` +- Update `src/specify_cli/__init__.py` - docs: fix the paths of generated files (moved under a `.specify/` folder) -- Update src/specify_cli/__init__.py +- Update `src/specify_cli/__init__.py` - feat: support 'specify init .' for current directory initialization - feat: Add emacs-style up/down keys @@ -2441,7 +2441,7 @@ - Update CONTRIBUTING.md - Refine `plan-template.md` with improved project type detection, clarified structure decision process, and enhanced research task guidance. -- Update __init__.py +- Update `__init__.py` ## [0.0.53] - 2025-09-24 @@ -2551,23 +2551,23 @@ - Update update-agent-context.ps1 - Update package release - Update config -- Update __init__.py -- Update __init__.py +- Update `__init__.py` +- Update `__init__.py` - Remove Codex-specific logic in the initialization script - Update version rev -- Update __init__.py +- Update `__init__.py` - Enhance Codex support by auto-syncing prompt files, allowing spec generation without git, and documenting clearer /specify usage. - Consistency tweaks - Consistent step coloring -- Update __init__.py -- Update __init__.py +- Update `__init__.py` +- Update `__init__.py` - Quick UI tweak - Update package release - Limit workspace command seeding to Codex init and update Codex documentation accordingly. - Clarify Codex-specific README note with rationale for its different workflow. - Bump to 0.0.7 and document Codex support - Normalize Codex command templates to the scripts-based schema and auto-upgrade generated commands. -- Fix remaining merge conflict markers in __init__.py +- Fix remaining merge conflict markers in `__init__.py` - Add Codex CLI support with AGENTS.md and commands bootstrap ## [0.0.45] - 2025-09-19 @@ -2583,7 +2583,7 @@ ### Changed - Update specify.md -- Update __init__.py +- Update `__init__.py` ## [0.0.43] - 2025-09-18 @@ -2721,11 +2721,11 @@ ### Changed - Update config -- Update __init__.py +- Update `__init__.py` - Refactor with platform-specific constraints - Update README.md - Update CLI reference -- Update __init__.py +- Update `__init__.py` - refactor: extract Claude local path to constant for maintainability - fix: support Claude CLI installed via migrate-installer diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 96818dba35..e87e0d292f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -257,7 +257,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] @@ -265,13 +265,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 @@ -303,7 +303,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 082c22ac7d..3b0ea0faf9 100644 --- a/docs/index.md +++ b/docs/index.md @@ -6,7 +6,7 @@ **Spec-Driven Development or your own process — step by step or as an automated workflow.** -Spec Kit is an extensible, intent-driven harness that pushes any coding agent beyond code, guiding it across your SDLC or any business process. Use it for [Spec-Driven Development](concepts/sdd.md) (SDD), where you describe _what_ to build and refine it through structured phases. Run it step by step, automate it end to end, or shape a process of your own, keeping intent at the center. +Spec Kit is an extensible, intent-driven harness that pushes any coding agent beyond code, guiding it across your SDLC or any business process. Use it for [Spec-Driven Development](concepts/sdd.md) (SDD), where you describe *what* to build and refine it through structured phases. Run it step by step, automate it end to end, or shape a process of your own, keeping intent at the center. Install Spec Kit  Quick Start @@ -19,6 +19,9 @@ Spec Kit is an extensible, intent-driven harness that pushes any coding agent be
+ + ### Spec-driven by default The core SDD process ships ready to use: **Spec → Plan → Tasks → Implement**. 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 67b69505e6..b41828d2ef 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/presets/lean/commands/speckit.specify.md b/presets/lean/commands/speckit.specify.md index c15353557a..4d794ed291 100644 --- a/presets/lean/commands/speckit.specify.md +++ b/presets/lean/commands/speckit.specify.md @@ -13,6 +13,7 @@ $ARGUMENTS 1. **Ask the user** for the feature directory path (e.g., `specs/my-feature`). Do not proceed until provided. 2. Create the directory and write `.specify/feature.json`: + ```json { "feature_directory": "" } ``` diff --git a/presets/scaffold/README.md b/presets/scaffold/README.md index b30a1ab6ac..ab27ee7e5e 100644 --- a/presets/scaffold/README.md +++ b/presets/scaffold/README.md @@ -23,6 +23,7 @@ A custom preset for Spec Kit. Copy this directory and customize it to create you ## Manifest Reference (`preset.yml`) Required fields: + - `schema_version` — always `"1.0"` - `preset.id` — lowercase alphanumeric with hyphens - `preset.name` — human-readable name diff --git a/templates/commands/analyze.md b/templates/commands/analyze.md index 7de2d28f0d..3348bb49fa 100644 --- a/templates/commands/analyze.md +++ b/templates/commands/analyze.md @@ -17,6 +17,7 @@ You **MUST** consider the user input before proceeding (if not empty). ## Pre-Execution Checks **Check for extension hooks (before analysis)**: + - Check if `.specify/extensions.yml` exists in the project root. - If it exists, read it and look for entries under the `hooks.before_analyze` key - If the YAML cannot be parsed or is invalid, do not skip silently: tell the user that `.specify/extensions.yml` could not be read (include the parser error) and that no hooks were checked, including any mandatory (`optional: false`) hooks registered there, then continue normally @@ -26,7 +27,8 @@ You **MUST** consider the user input before proceeding (if not empty). - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation - For each executable hook, output the following based on its `optional` flag: - **Optional hook** (`optional: true`): - ``` + + ```markdown ## Extension Hooks **Optional Pre-Hook**: {extension} @@ -36,8 +38,10 @@ You **MUST** consider the user input before proceeding (if not empty). Prompt: {prompt} To execute: `/{command}` ``` + - **Mandatory hook** (`optional: false`): - ``` + + ```markdown ## Extension Hooks **Automatic Pre-Hook**: {extension} @@ -46,6 +50,7 @@ You **MUST** consider the user input before proceeding (if not empty). Wait for the result of the hook command before proceeding to the Goal. ``` + After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook. - If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently @@ -195,7 +200,7 @@ At end of report, output a concise Next Actions block: - If CRITICAL issues exist: Recommend resolving before `__SPECKIT_COMMAND_IMPLEMENT__` - If only LOW/MEDIUM: User may proceed, but provide improvement suggestions -- Provide explicit command suggestions: e.g., "Run __SPECKIT_COMMAND_SPECIFY__ with refinement", "Run __SPECKIT_COMMAND_PLAN__ to adjust architecture", "Manually edit tasks.md to add coverage for 'performance-metrics'" +- Provide explicit command suggestions: e.g., "Run `__SPECKIT_COMMAND_SPECIFY__` with refinement", "Run `__SPECKIT_COMMAND_PLAN__` to adjust architecture", "Manually edit tasks.md to add coverage for 'performance-metrics'" ### 8. Offer Remediation @@ -204,6 +209,7 @@ Ask the user: "Would you like me to suggest concrete remediation edits for the t ### 9. Check for extension hooks After reporting, check if `.specify/extensions.yml` exists in the project root. + - If it exists, read it and look for entries under the `hooks.after_analyze` key - If the YAML cannot be parsed or is invalid, do not skip silently: tell the user that `.specify/extensions.yml` could not be read (include the parser error) and that no hooks were checked, including any mandatory (`optional: false`) hooks registered there, then continue normally - Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default. @@ -212,7 +218,8 @@ After reporting, check if `.specify/extensions.yml` exists in the project root. - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation - For each executable hook, output the following based on its `optional` flag: - **Optional hook** (`optional: true`): - ``` + + ```markdown ## Extension Hooks **Optional Hook**: {extension} @@ -222,14 +229,17 @@ After reporting, check if `.specify/extensions.yml` exists in the project root. Prompt: {prompt} To execute: `/{command}` ``` + - **Mandatory hook** (`optional: false`): - ``` + + ```markdown ## Extension Hooks **Automatic Hook**: {extension} Executing: `/{command}` EXECUTE_COMMAND: {command} ``` + After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook. - If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently diff --git a/templates/commands/checklist.md b/templates/commands/checklist.md index 6e33f5d422..184d181913 100644 --- a/templates/commands/checklist.md +++ b/templates/commands/checklist.md @@ -47,6 +47,7 @@ You **MUST** consider the user input before proceeding (if not empty). ## Pre-Execution Checks **Check for extension hooks (before checklist generation)**: + - Check if `.specify/extensions.yml` exists in the project root. - If it exists, read it and look for entries under the `hooks.before_checklist` key - If the YAML cannot be parsed or is invalid, do not skip silently: tell the user that `.specify/extensions.yml` could not be read (include the parser error) and that no hooks were checked, including any mandatory (`optional: false`) hooks registered there, then continue normally @@ -56,7 +57,8 @@ You **MUST** consider the user input before proceeding (if not empty). - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation - For each executable hook, output the following based on its `optional` flag: - **Optional hook** (`optional: true`): - ``` + + ```markdown ## Extension Hooks **Optional Pre-Hook**: {extension} @@ -66,8 +68,10 @@ You **MUST** consider the user input before proceeding (if not empty). Prompt: {prompt} To execute: `/{command}` ``` + - **Mandatory hook** (`optional: false`): - ``` + + ```markdown ## Extension Hooks **Automatic Pre-Hook**: {extension} @@ -76,6 +80,7 @@ You **MUST** consider the user input before proceeding (if not empty). Wait for the result of the hook command before proceeding to the Execution Steps. ``` + After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook. - If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently @@ -349,6 +354,7 @@ Sample items: **Check for extension hooks (after checklist generation)**: Check if `.specify/extensions.yml` exists in the project root. + - If it exists, read it and look for entries under the `hooks.after_checklist` key - If the YAML cannot be parsed or is invalid, do not skip silently: tell the user that `.specify/extensions.yml` could not be read (include the parser error) and that no hooks were checked, including any mandatory (`optional: false`) hooks registered there, then continue normally - Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default. @@ -357,7 +363,8 @@ Check if `.specify/extensions.yml` exists in the project root. - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation - For each executable hook, output the following based on its `optional` flag: - **Optional hook** (`optional: true`): - ``` + + ```markdown ## Extension Hooks **Optional Hook**: {extension} @@ -367,13 +374,16 @@ Check if `.specify/extensions.yml` exists in the project root. Prompt: {prompt} To execute: `/{command}` ``` + - **Mandatory hook** (`optional: false`): - ``` + + ```markdown ## Extension Hooks **Automatic Hook**: {extension} Executing: `/{command}` EXECUTE_COMMAND: {command} ``` + After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook. - If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently diff --git a/templates/commands/clarify.md b/templates/commands/clarify.md index 8663b6908b..7798deb2b7 100644 --- a/templates/commands/clarify.md +++ b/templates/commands/clarify.md @@ -21,6 +21,7 @@ You **MUST** consider the user input before proceeding (if not empty). ## Pre-Execution Checks **Check for extension hooks (before clarification)**: + - Check if `.specify/extensions.yml` exists in the project root. - If it exists, read it and look for entries under the `hooks.before_clarify` key - If the YAML cannot be parsed or is invalid, do not skip silently: tell the user that `.specify/extensions.yml` could not be read (include the parser error) and that no hooks were checked, including any mandatory (`optional: false`) hooks registered there, then continue normally @@ -30,7 +31,8 @@ You **MUST** consider the user input before proceeding (if not empty). - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation - For each executable hook, output the following based on its `optional` flag: - **Optional hook** (`optional: true`): - ``` + + ```markdown ## Extension Hooks **Optional Pre-Hook**: {extension} @@ -40,8 +42,10 @@ You **MUST** consider the user input before proceeding (if not empty). Prompt: {prompt} To execute: `/{command}` ``` + - **Mandatory hook** (`optional: false`): - ``` + + ```markdown ## Extension Hooks **Automatic Pre-Hook**: {extension} @@ -50,6 +54,7 @@ You **MUST** consider the user input before proceeding (if not empty). Wait for the result of the hook command before proceeding to the Outline. ``` + After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook. - If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently @@ -243,6 +248,7 @@ Context for prioritization: {ARGS} **You MUST complete this section before reporting completion to the user.** Check if `.specify/extensions.yml` exists in the project root. + - If it does not exist, or no hooks are registered under `hooks.after_clarify`, skip to the Completion Report. - If it exists, read it and look for entries under the `hooks.after_clarify` key. - If the YAML cannot be parsed or is invalid, do not skip silently: tell the user that `.specify/extensions.yml` could not be read (include the parser error) and that no hooks were checked, including any mandatory (`optional: false`) hooks registered there, then continue to the Completion Report. @@ -252,16 +258,19 @@ Check if `.specify/extensions.yml` exists in the project root. - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation - For each executable hook, output the following based on its `optional` flag: - **Mandatory hook** (`optional: false`) — **You MUST emit `EXECUTE_COMMAND:` for each mandatory hook**: - ``` + + ```markdown ## Extension Hooks **Automatic Hook**: {extension} Executing: `/{command}` EXECUTE_COMMAND: {command} ``` + After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook. - **Optional hook** (`optional: true`): - ``` + + ```markdown ## Extension Hooks **Optional Hook**: {extension} @@ -275,6 +284,7 @@ Check if `.specify/extensions.yml` exists in the project root. ## Completion Report Report completion (after questioning loop ends or early termination): + - Number of questions asked & answered. - Path to updated spec. - Sections touched (list names). diff --git a/templates/commands/constitution.md b/templates/commands/constitution.md index f2c107a158..8148573af3 100644 --- a/templates/commands/constitution.md +++ b/templates/commands/constitution.md @@ -40,6 +40,7 @@ and commands read the constitution at runtime and are not modified here. ## Pre-Execution Checks **Check for extension hooks (before constitution update)**: + - Check if `.specify/extensions.yml` exists in the project root. - If it exists, read it and look for entries under the `hooks.before_constitution` key - If the YAML cannot be parsed or is invalid, do not skip silently: tell the user that `.specify/extensions.yml` could not be read (include the parser error) and that no hooks were checked, including any mandatory (`optional: false`) hooks registered there, then continue normally @@ -49,7 +50,8 @@ and commands read the constitution at runtime and are not modified here. - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation - For each executable hook, output the following based on its `optional` flag: - **Optional hook** (`optional: true`): - ``` + + ```markdown ## Extension Hooks **Optional Pre-Hook**: {extension} @@ -59,8 +61,10 @@ and commands read the constitution at runtime and are not modified here. Prompt: {prompt} To execute: `/{command}` ``` + - **Mandatory hook** (`optional: false`): - ``` + + ```markdown ## Extension Hooks **Automatic Pre-Hook**: {extension} @@ -69,6 +73,7 @@ and commands read the constitution at runtime and are not modified here. Wait for the result of the hook command before proceeding to the Outline. ``` + After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook. - If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently @@ -149,6 +154,7 @@ Write only `.specify/memory/constitution.md`; do not create or modify template s **Check for extension hooks (after constitution update)**: Check if `.specify/extensions.yml` exists in the project root. + - If it exists, read it and look for entries under the `hooks.after_constitution` key - If the YAML cannot be parsed or is invalid, do not skip silently: tell the user that `.specify/extensions.yml` could not be read (include the parser error) and that no hooks were checked, including any mandatory (`optional: false`) hooks registered there, then continue normally - Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default. @@ -157,7 +163,8 @@ Check if `.specify/extensions.yml` exists in the project root. - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation - For each executable hook, output the following based on its `optional` flag: - **Optional hook** (`optional: true`): - ``` + + ```markdown ## Extension Hooks **Optional Hook**: {extension} @@ -167,13 +174,16 @@ Check if `.specify/extensions.yml` exists in the project root. Prompt: {prompt} To execute: `/{command}` ``` + - **Mandatory hook** (`optional: false`): - ``` + + ```markdown ## Extension Hooks **Automatic Hook**: {extension} Executing: `/{command}` EXECUTE_COMMAND: {command} ``` + After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook. - If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently diff --git a/templates/commands/converge.md b/templates/commands/converge.md index 5d29b74db6..4757c98991 100644 --- a/templates/commands/converge.md +++ b/templates/commands/converge.md @@ -50,6 +50,7 @@ You **MUST** consider the user input before proceeding (if not empty). Wait for the result of the hook command before proceeding to the Goal. ``` + After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook. - If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently @@ -268,6 +269,7 @@ After producing the result, check if `.specify/extensions.yml` exists in the pro Executing: `/{command}` EXECUTE_COMMAND: {command} ``` + After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook. - If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently diff --git a/templates/commands/implement.md b/templates/commands/implement.md index f98ba525de..a90b626016 100644 --- a/templates/commands/implement.md +++ b/templates/commands/implement.md @@ -17,6 +17,7 @@ You **MUST** consider the user input before proceeding (if not empty). ## Pre-Execution Checks **Check for extension hooks (before implementation)**: + - Check if `.specify/extensions.yml` exists in the project root. - If it exists, read it and look for entries under the `hooks.before_implement` key - If the YAML cannot be parsed or is invalid, do not skip silently: tell the user that `.specify/extensions.yml` could not be read (include the parser error) and that no hooks were checked, including any mandatory (`optional: false`) hooks registered there, then continue normally @@ -26,7 +27,8 @@ You **MUST** consider the user input before proceeding (if not empty). - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation - For each executable hook, output the following based on its `optional` flag: - **Optional hook** (`optional: true`): - ``` + + ```markdown ## Extension Hooks **Optional Pre-Hook**: {extension} @@ -36,8 +38,10 @@ You **MUST** consider the user input before proceeding (if not empty). Prompt: {prompt} To execute: `/{command}` ``` + - **Mandatory hook** (`optional: false`): - ``` + + ```markdown ## Extension Hooks **Automatic Pre-Hook**: {extension} @@ -46,6 +50,7 @@ You **MUST** consider the user input before proceeding (if not empty). Wait for the result of the hook command before proceeding to the Outline. ``` + After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook. - If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently @@ -181,6 +186,7 @@ Note: This command assumes a complete task breakdown exists in tasks.md. If task **You MUST complete this section before reporting completion to the user.** Check if `.specify/extensions.yml` exists in the project root. + - If it does not exist, or no hooks are registered under `hooks.after_implement`, skip to the Completion Report. - If it exists, read it and look for entries under the `hooks.after_implement` key. - If the YAML cannot be parsed or is invalid, do not skip silently: tell the user that `.specify/extensions.yml` could not be read (include the parser error) and that no hooks were checked, including any mandatory (`optional: false`) hooks registered there, then continue to the Completion Report. @@ -190,16 +196,19 @@ Check if `.specify/extensions.yml` exists in the project root. - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation - For each executable hook, output the following based on its `optional` flag: - **Mandatory hook** (`optional: false`) — **You MUST emit `EXECUTE_COMMAND:` for each mandatory hook**: - ``` + + ```markdown ## Extension Hooks **Automatic Hook**: {extension} Executing: `/{command}` EXECUTE_COMMAND: {command} ``` + After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook. - **Optional hook** (`optional: true`): - ``` + + ```markdown ## Extension Hooks **Optional Hook**: {extension} diff --git a/templates/commands/plan.md b/templates/commands/plan.md index d559e517ae..43e48b2bf4 100644 --- a/templates/commands/plan.md +++ b/templates/commands/plan.md @@ -25,6 +25,7 @@ You **MUST** consider the user input before proceeding (if not empty). ## Pre-Execution Checks **Check for extension hooks (before planning)**: + - Check if `.specify/extensions.yml` exists in the project root. - If it exists, read it and look for entries under the `hooks.before_plan` key - If the YAML cannot be parsed or is invalid, do not skip silently: tell the user that `.specify/extensions.yml` could not be read (include the parser error) and that no hooks were checked, including any mandatory (`optional: false`) hooks registered there, then continue normally @@ -34,7 +35,8 @@ You **MUST** consider the user input before proceeding (if not empty). - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation - For each executable hook, output the following based on its `optional` flag: - **Optional hook** (`optional: true`): - ``` + + ```markdown ## Extension Hooks **Optional Pre-Hook**: {extension} @@ -44,8 +46,10 @@ You **MUST** consider the user input before proceeding (if not empty). Prompt: {prompt} To execute: `/{command}` ``` + - **Mandatory hook** (`optional: false`): - ``` + + ```markdown ## Extension Hooks **Automatic Pre-Hook**: {extension} @@ -54,6 +58,7 @@ You **MUST** consider the user input before proceeding (if not empty). Wait for the result of the hook command before proceeding to the Outline. ``` + After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook. - If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently @@ -76,6 +81,7 @@ You **MUST** consider the user input before proceeding (if not empty). **You MUST complete this section before reporting completion to the user.** Check if `.specify/extensions.yml` exists in the project root. + - If it does not exist, or no hooks are registered under `hooks.after_plan`, skip to the Completion Report. - If it exists, read it and look for entries under the `hooks.after_plan` key. - If the YAML cannot be parsed or is invalid, do not skip silently: tell the user that `.specify/extensions.yml` could not be read (include the parser error) and that no hooks were checked, including any mandatory (`optional: false`) hooks registered there, then continue to the Completion Report. @@ -85,16 +91,19 @@ Check if `.specify/extensions.yml` exists in the project root. - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation - For each executable hook, output the following based on its `optional` flag: - **Mandatory hook** (`optional: false`) — **You MUST emit `EXECUTE_COMMAND:` for each mandatory hook**: - ``` + + ```markdown ## Extension Hooks **Automatic Hook**: {extension} Executing: `/{command}` EXECUTE_COMMAND: {command} ``` + After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook. - **Optional hook** (`optional: true`): - ``` + + ```markdown ## Extension Hooks **Optional Hook**: {extension} diff --git a/templates/commands/specify.md b/templates/commands/specify.md index 52b2b24565..047e8e6452 100644 --- a/templates/commands/specify.md +++ b/templates/commands/specify.md @@ -21,6 +21,7 @@ You **MUST** consider the user input before proceeding (if not empty). ## Pre-Execution Checks **Check for extension hooks (before specification)**: + - Check if `.specify/extensions.yml` exists in the project root. - If it exists, read it and look for entries under the `hooks.before_specify` key - If the YAML cannot be parsed or is invalid, do not skip silently: tell the user that `.specify/extensions.yml` could not be read (include the parser error) and that no hooks were checked, including any mandatory (`optional: false`) hooks registered there, then continue normally @@ -30,7 +31,8 @@ You **MUST** consider the user input before proceeding (if not empty). - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation - For each executable hook, output the following based on its `optional` flag: - **Optional hook** (`optional: true`): - ``` + + ```markdown ## Extension Hooks **Optional Pre-Hook**: {extension} @@ -40,8 +42,10 @@ You **MUST** consider the user input before proceeding (if not empty). Prompt: {prompt} To execute: `/{command}` ``` + - **Mandatory hook** (`optional: false`): - ``` + + ```markdown ## Extension Hooks **Automatic Pre-Hook**: {extension} @@ -50,6 +54,7 @@ You **MUST** consider the user input before proceeding (if not empty). Wait for the result of the hook command before proceeding to the Outline. ``` + After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook. - If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently @@ -97,11 +102,13 @@ Given that feature description, do this: - Copy the resolved `spec-template` file to `SPECIFY_FEATURE_DIRECTORY/spec.md` as the starting point - Set `SPEC_FILE` to `SPECIFY_FEATURE_DIRECTORY/spec.md` - Persist the resolved path to `.specify/feature.json`: + ```json { "feature_directory": "" } ``` + Write the actual resolved directory path value (for example, `specs/003-user-auth`), not the literal string `SPECIFY_FEATURE_DIRECTORY`. This allows downstream commands (`__SPECKIT_COMMAND_PLAN__`, `__SPECKIT_COMMAND_TASKS__`, etc.) to locate the feature directory without relying on git branch name conventions. @@ -238,6 +245,7 @@ Given that feature description, do this: **You MUST complete this section before reporting completion to the user.** Check if `.specify/extensions.yml` exists in the project root. + - If it does not exist, or no hooks are registered under `hooks.after_specify`, skip to the Completion Report. - If it exists, read it and look for entries under the `hooks.after_specify` key. - If the YAML cannot be parsed or is invalid, do not skip silently: tell the user that `.specify/extensions.yml` could not be read (include the parser error) and that no hooks were checked, including any mandatory (`optional: false`) hooks registered there, then continue to the Completion Report. @@ -247,16 +255,19 @@ Check if `.specify/extensions.yml` exists in the project root. - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation - For each executable hook, output the following based on its `optional` flag: - **Mandatory hook** (`optional: false`) — **You MUST emit `EXECUTE_COMMAND:` for each mandatory hook**: - ``` + + ```markdown ## Extension Hooks **Automatic Hook**: {extension} Executing: `/{command}` EXECUTE_COMMAND: {command} ``` + After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook. - **Optional hook** (`optional: true`): - ``` + + ```markdown ## Extension Hooks **Optional Hook**: {extension} @@ -270,6 +281,7 @@ Check if `.specify/extensions.yml` exists in the project root. ## Completion Report Report completion to the user with: + - `SPECIFY_FEATURE_DIRECTORY` — the feature directory path - `SPEC_FILE` — the spec file path - Checklist results summary diff --git a/templates/commands/tasks.md b/templates/commands/tasks.md index 1b9c4255fe..080b920c3e 100644 --- a/templates/commands/tasks.md +++ b/templates/commands/tasks.md @@ -26,6 +26,7 @@ You **MUST** consider the user input before proceeding (if not empty). ## Pre-Execution Checks **Check for extension hooks (before tasks generation)**: + - Check if `.specify/extensions.yml` exists in the project root. - If it exists, read it and look for entries under the `hooks.before_tasks` key - If the YAML cannot be parsed or is invalid, do not skip silently: tell the user that `.specify/extensions.yml` could not be read (include the parser error) and that no hooks were checked, including any mandatory (`optional: false`) hooks registered there, then continue normally @@ -35,7 +36,8 @@ You **MUST** consider the user input before proceeding (if not empty). - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation - For each executable hook, output the following based on its `optional` flag: - **Optional hook** (`optional: true`): - ``` + + ```markdown ## Extension Hooks **Optional Pre-Hook**: {extension} @@ -45,8 +47,10 @@ You **MUST** consider the user input before proceeding (if not empty). Prompt: {prompt} To execute: `/{command}` ``` + - **Mandatory hook** (`optional: false`): - ``` + + ```markdown ## Extension Hooks **Automatic Pre-Hook**: {extension} @@ -55,6 +59,7 @@ You **MUST** consider the user input before proceeding (if not empty). Wait for the result of the hook command before proceeding to the Outline. ``` + After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook. - If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently @@ -97,6 +102,7 @@ You **MUST** consider the user input before proceeding (if not empty). **You MUST complete this section before reporting completion to the user.** Check if `.specify/extensions.yml` exists in the project root. + - If it does not exist, or no hooks are registered under `hooks.after_tasks`, skip to the Completion Report. - If it exists, read it and look for entries under the `hooks.after_tasks` key. - If the YAML cannot be parsed or is invalid, do not skip silently: tell the user that `.specify/extensions.yml` could not be read (include the parser error) and that no hooks were checked, including any mandatory (`optional: false`) hooks registered there, then continue to the Completion Report. @@ -106,16 +112,19 @@ Check if `.specify/extensions.yml` exists in the project root. - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation - For each executable hook, output the following based on its `optional` flag: - **Mandatory hook** (`optional: false`) — **You MUST emit `EXECUTE_COMMAND:` for each mandatory hook**: - ``` + + ```markdown ## Extension Hooks **Automatic Hook**: {extension} Executing: `/{command}` EXECUTE_COMMAND: {command} ``` + After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook. - **Optional hook** (`optional: true`): - ``` + + ```markdown ## Extension Hooks **Optional Hook**: {extension} @@ -129,6 +138,7 @@ Check if `.specify/extensions.yml` exists in the project root. ## Completion Report Output path to generated tasks.md and summary: + - Total task count - Task count per user story - Parallel opportunities identified diff --git a/templates/commands/taskstoissues.md b/templates/commands/taskstoissues.md index f982448906..eae1406dbb 100644 --- a/templates/commands/taskstoissues.md +++ b/templates/commands/taskstoissues.md @@ -18,6 +18,7 @@ You **MUST** consider the user input before proceeding (if not empty). ## Pre-Execution Checks **Check for extension hooks (before tasks-to-issues conversion)**: + - Check if `.specify/extensions.yml` exists in the project root. - If it exists, read it and look for entries under the `hooks.before_taskstoissues` key - If the YAML cannot be parsed or is invalid, do not skip silently: tell the user that `.specify/extensions.yml` could not be read (include the parser error) and that no hooks were checked, including any mandatory (`optional: false`) hooks registered there, then continue normally @@ -27,7 +28,8 @@ You **MUST** consider the user input before proceeding (if not empty). - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation - For each executable hook, output the following based on its `optional` flag: - **Optional hook** (`optional: true`): - ``` + + ```markdown ## Extension Hooks **Optional Pre-Hook**: {extension} @@ -37,8 +39,10 @@ You **MUST** consider the user input before proceeding (if not empty). Prompt: {prompt} To execute: `/{command}` ``` + - **Mandatory hook** (`optional: false`): - ``` + + ```markdown ## Extension Hooks **Automatic Pre-Hook**: {extension} @@ -47,6 +51,7 @@ You **MUST** consider the user input before proceeding (if not empty). Wait for the result of the hook command before proceeding to the Outline. ``` + After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook. - If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently @@ -76,6 +81,7 @@ git config --get remote.origin.url **Check for extension hooks (after tasks-to-issues conversion)**: Check if `.specify/extensions.yml` exists in the project root. + - If it exists, read it and look for entries under the `hooks.after_taskstoissues` key - If the YAML cannot be parsed or is invalid, do not skip silently: tell the user that `.specify/extensions.yml` could not be read (include the parser error) and that no hooks were checked, including any mandatory (`optional: false`) hooks registered there, then continue normally - Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default. @@ -84,7 +90,8 @@ Check if `.specify/extensions.yml` exists in the project root. - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation - For each executable hook, output the following based on its `optional` flag: - **Optional hook** (`optional: true`): - ``` + + ```markdown ## Extension Hooks **Optional Hook**: {extension} @@ -94,13 +101,16 @@ Check if `.specify/extensions.yml` exists in the project root. Prompt: {prompt} To execute: `/{command}` ``` + - **Mandatory hook** (`optional: false`): - ``` + + ```markdown ## Extension Hooks **Automatic Hook**: {extension} Executing: `/{command}` EXECUTE_COMMAND: {command} ``` + After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook. - If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently 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 From 400fd09004eace962e43bb8203d966ae338fd40d Mon Sep 17 00:00:00 2001 From: SurefireStudios <123013554+SurefireStudios@users.noreply.github.com> Date: Wed, 16 Sep 2026 13:53:11 -0700 Subject: [PATCH 2/3] fix(ci): fail the glob check per glob, not on the total The check summed matches across every glob and only failed at zero, so one stale entry among several still passed while leaving that part of the documentation unlinted -- the same silent narrowing as #4526, just partial. Each glob is now checked on its own and every empty one is reported. The unmatched globs are collected in an array rather than a string: nullglob is on for this step, so re-expanding an unquoted list of globs that match nothing erases the list before it can be printed. --- .github/workflows/lint.yml | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index c1a35d6ee0..9084b8d981 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -62,17 +62,28 @@ jobs: 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" ] && count=$((count + 1)) + [ -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 [ "$count" -eq 0 ]; then - echo "::error::markdownlint globs matched no files; the lint job would pass without checking anything (see #4526)" + if [ ${#empty[@]} -gt 0 ]; then + for glob in "${empty[@]}"; do + echo "::error::markdownlint glob matches no files: $glob (see #4526)" + done exit 1 fi From 2390673c9c3d570b90686e9022dd015adab480d4 Mon Sep 17 00:00:00 2001 From: SurefireStudios <123013554+SurefireStudios@users.noreply.github.com> Date: Fri, 18 Sep 2026 12:30:43 -0700 Subject: [PATCH 3/3] fix(ci): exclude CHANGELOG.md from the documentation allowlist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per review: the changelog is generated from git commit messages by the release workflow, so linting it is pointless and the edits this branch made to it were worse than pointless — they rewrote commit-message text that the next release regenerates, making the file diverge from the commits it is built from. Dropped CHANGELOG.md from DOC_GLOBS and reverted the file to its base state. The allowlist now matches 56 files, 0 errors, and the guard step agrees. --- .github/workflows/lint.yml | 1 - CHANGELOG.md | 54 +++++++++++++++++++------------------- 2 files changed, 27 insertions(+), 28 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 9084b8d981..6b11a1d09a 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -15,7 +15,6 @@ jobs: docs/**/*.md README.md README.zh-CN.md - CHANGELOG.md CODE_OF_CONDUCT.md CONTRIBUTING.md DEVELOPMENT.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b75796fae..d2534c829f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -487,7 +487,7 @@ - fix(agent-context): apply default markers when config markers are blank (bash) (#3736) - fix: escape Rich markup in catalog list output (#3738) - fix(workflows): guard non-mapping 'workflow:' block in WorkflowDefinition (#3694) -- fix(bundler): reject unsupported schema_version in `_merge_config` (align readers) (#3711) +- fix(bundler): reject unsupported schema_version in _merge_config (align readers) (#3711) - Update Linear Weave extension to v1.0.1 (#3762) - Add Intake Sequencing Governance preset to community catalog (#3761) - Update Quality Gates (Enforcement Layer) extension to v0.3.3 (#3760) @@ -602,7 +602,7 @@ ### Changed - fix(integrations): escape Rich markup in --integration-options error messages (#3458) -- docs: document `__SPECKIT_COMMAND_` token for portable cross-command references (#3503) +- docs: document __SPECKIT_COMMAND_ token for portable cross-command references (#3503) - [preset] Add Parallel Autonomous Run Governance preset to community catalog (#3614) - docs(workflows): fix stale FanOutStep docstring claiming sequential-only execution (#3639) - [bundle] Add SicarioSpec Security & Governance Bundle to community catalog (#3636) @@ -1179,7 +1179,7 @@ - docs: document spec persistence models (#2856) - chore(catalog): bump Linear Integration to v0.3.0 (repo renamed to spec-kit-linear-sync) (#2893) - chore: update DocGuard extension to v0.25.0 (#2707) -- chore: remove unused `open_github_url/_StripAuthOnRedirect` from `_github_http.py` (#2883) +- chore: remove unused open_github_url/_StripAuthOnRedirect from _github_http.py (#2883) - fix(catalogs): validate extension and preset catalog payload shape (#2621) - feat(integration): add status reporting (#2674) - chore: release 0.10.0, begin 0.10.1.dev0 development (#2904) @@ -1316,7 +1316,7 @@ - docs: update landing page stats and branch naming convention (#2727) - feat(workflows): expose {{ context.run_id }} template variable (#2664) -- fix: resolve `__SPECKIT_COMMAND_*__` refs in preset skill rendering (#2717) (#2718) +- fix: resolve __SPECKIT_COMMAND_*__ refs in preset skill rendering (#2717) (#2718) - Add Workflow Preset to community catalog (#2725) - fix: paths-only skips branch validation, setup-plan preserves existing plan (#2672) - docs: fix broken pipx homepage URLs to point to pipx.pypa.io (#2670) @@ -1376,7 +1376,7 @@ - Add Team Assign extension to community catalog (#2642) - refactor: migrate extension catalog stack parsing to shared base (#2576) - Update Architecture Workflow extension to v1.1.0 (#2588) -- fix(workflow): support integration: auto to follow project's initialized AI (#2421) +- fix(workflow): support integration: auto to follow project's initialized AI (#2421) - Add Superpowers Implementation Bridge extension to community catalog (#2586) - Add Interactive HTML Preview extension to community catalog (#2585) - chore: release 0.8.11, begin 0.8.12.dev0 development (#2584) @@ -1386,7 +1386,7 @@ ### Changed -- refactor: extract `_version.py` from `__init__.py` (PR-3/8) (#2550) +- refactor: extract _version.py from __init__.py (PR-3/8) (#2550) - Add Time Machine extension to community catalog (#2580) - fix(powershell): ensure UTF-8 templates are written without BOM (#2280) - docs: document high-assurance spec workflow (#2518) @@ -1406,9 +1406,9 @@ - Add Agent Governance extension to community catalog (#2559) - Add Reqnroll BDD extension to community catalog (#2545) - fix(cli): harden extension registration and discovery workflows (#2499) -- refactor: extract `_assets.py` and `_utils.py` from `__init__.py` (PR-2/8) (#2543) +- refactor: extract _assets.py and _utils.py from __init__.py (PR-2/8) (#2543) - fix(opencode): use commands/ directory (plural) to match OpenCode docs (#2453) -- refactor: extract `_console.py` from `__init__.py` (PR-1/8) (#2474) +- refactor: extract _console.py from __init__.py (PR-1/8) (#2474) - Fix constitution reference in README (#2491) - chore: release 0.8.9, begin 0.8.10.dev0 development (#2532) @@ -2192,15 +2192,15 @@ - Update CHANGELOG.md - Update agent configuration - Update scripts/powershell/create-new-feature.ps1 -- Update `src/specify_cli/__init__.py` +- Update src/specify_cli/__init__.py - Create create-release-packages.ps1 - Script changes - Update taskstoissues.md - Create taskstoissues.md -- Update `src/specify_cli/__init__.py` +- Update src/specify_cli/__init__.py - Update CONTRIBUTING.md - Potential fix for code scanning alert no. 3: Workflow does not contain permissions -- Update `src/specify_cli/__init__.py` +- Update src/specify_cli/__init__.py - Update CHANGELOG.md - Fixes #970 - Fixes #975 @@ -2383,13 +2383,13 @@ ### Changed -- Update `__init__.py` +- Update __init__.py - Consolidate Cursor naming - Update CHANGELOG.md - Git errors are now highlighted. -- Update `__init__.py` +- Update __init__.py - Refactor agent configuration -- Update `src/specify_cli/__init__.py` +- Update src/specify_cli/__init__.py - Update scripts/powershell/update-agent-context.ps1 - Update AGENTS.md - Update templates/commands/implement.md @@ -2453,10 +2453,10 @@ - Update URLs to Contributing and Support Guides in Docs - fix: add UTF-8 encoding to file read/write operations in update-agent-context.ps1 -- Update `__init__.py` -- Update `src/specify_cli/__init__.py` +- Update __init__.py +- Update src/specify_cli/__init__.py - docs: fix the paths of generated files (moved under a `.specify/` folder) -- Update `src/specify_cli/__init__.py` +- Update src/specify_cli/__init__.py - feat: support 'specify init .' for current directory initialization - feat: Add emacs-style up/down keys @@ -2466,7 +2466,7 @@ - Update CONTRIBUTING.md - Refine `plan-template.md` with improved project type detection, clarified structure decision process, and enhanced research task guidance. -- Update `__init__.py` +- Update __init__.py ## [0.0.53] - 2025-09-24 @@ -2576,23 +2576,23 @@ - Update update-agent-context.ps1 - Update package release - Update config -- Update `__init__.py` -- Update `__init__.py` +- Update __init__.py +- Update __init__.py - Remove Codex-specific logic in the initialization script - Update version rev -- Update `__init__.py` +- Update __init__.py - Enhance Codex support by auto-syncing prompt files, allowing spec generation without git, and documenting clearer /specify usage. - Consistency tweaks - Consistent step coloring -- Update `__init__.py` -- Update `__init__.py` +- Update __init__.py +- Update __init__.py - Quick UI tweak - Update package release - Limit workspace command seeding to Codex init and update Codex documentation accordingly. - Clarify Codex-specific README note with rationale for its different workflow. - Bump to 0.0.7 and document Codex support - Normalize Codex command templates to the scripts-based schema and auto-upgrade generated commands. -- Fix remaining merge conflict markers in `__init__.py` +- Fix remaining merge conflict markers in __init__.py - Add Codex CLI support with AGENTS.md and commands bootstrap ## [0.0.45] - 2025-09-19 @@ -2608,7 +2608,7 @@ ### Changed - Update specify.md -- Update `__init__.py` +- Update __init__.py ## [0.0.43] - 2025-09-18 @@ -2746,11 +2746,11 @@ ### Changed - Update config -- Update `__init__.py` +- Update __init__.py - Refactor with platform-specific constraints - Update README.md - Update CLI reference -- Update `__init__.py` +- Update __init__.py - refactor: extract Claude local path to constant for maintainability - fix: support Claude CLI installed via migrate-installer