v0.13.1
216 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2df0394cb2 |
docs(integrations): document the 'integration list --catalog' flag (#3530)
* docs(integrations): document the 'integration list --catalog' flag 'specify integration list' accepts a --catalog flag (integrations/_query_commands.py: typer.Option(False, "--catalog", ...)) that browses the full built-in + community catalog, but the Integrations reference documented no options for the list command. Add an option table for it, matching the style used by the sibling 'integration search' and 'integration catalog add' sections. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(integrations): clarify that default 'integration list' shows only built-ins The --catalog row implied the default list already includes the full installed set; in fact 'integration list' iterates INTEGRATION_REGISTRY (built-ins) and marks installed status, so a community integration that is not built in only appears with --catalog. Reword the option and the intro sentence to say the default shows the built-in integrations and --catalog adds community ones. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
b17c70d6f0 |
Add OKF Knowledge Bundle Generator extension to community catalog (#3585)
Add okf extension submitted by @alexcpn to: - extensions/catalog.community.json (alphabetical order) - docs/community/extensions.md community extensions table Closes #3580 Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> |
||
|
|
a5b0bb3110 |
Update Autonomous Run Governance preset to v0.2.2 (#3584)
Update autonomous-run-governance preset submitted by @hindermath to: - presets/catalog.community.json (version, download_url, documentation, provides, tags, updated_at) - docs/community/presets.md community presets table Closes #3569 Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> |
||
|
|
7bdf6c5041 |
docs: weave harness/SDLC framing into landing page (#3567)
Reframe the docs landing hero and "Make it your own" pillar to inject the "harness" and "SDLC" framing while keeping SDD front and center: - Hero: describe Spec Kit as an extensible, intent-driven harness that pushes any coding agent beyond code, across the SDLC or any business process; tagline now contrasts step-by-step vs automated-workflow runs. - "Make it your own": explain the process lives in swappable building blocks (not locked to SDD or even software) and add a real non-software preset (Fiction Book Writing) to back the broadened scope. - Community blurb: drop "development" so "entirely new processes" matches the wider positioning. Assisted-by: GitHub Copilot (model: Claude Opus 4.8, supervised) Copilot-Session: 1cf71797-ac0d-4a5e-8266-784906933b54 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> |
||
|
|
396fc2c240 |
docs: reframe SDD positioning, modernize install, and de-duplicate walkthroughs (#3565)
* docs: reframe SDD positioning, modernize install, and de-duplicate walkthroughs Reframe the landing page so Spec Kit reads as a toolkit for Spec-Driven Development *or your own process* with any AI coding agent, and correct stale claims: context files and git are now opt-in extensions, and the install path uses PyPI (specify-cli). Generalize the landing cards to cover bundles and catalog hosting across all primitives. Restructure the Quick Start into a lean, guided Taskify walkthrough with one command per step (install as a prerequisite, Steps 1-9 aligned with the Full path), and extract the deep per-command detail into two new reference pages: reference/agentic-sdd.md (the /speckit.* SDD process) and reference/agentic-bugfix.md (the bug extension). Retitle the reference overview to "Reference" and group these agentic processes in their own section, distinct from CLI-managed primitives. Remove the duplicated "Detailed Process" walkthrough from README.md (and its TOC entry), repointing readers to the docs-site Quick Start while keeping the concise "Get Started" section as the front door. Assisted-by: GitHub Copilot (model: Claude Opus 4.8, supervised) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9d9232f8-ece4-4aa6-a9bd-ff8d74ca1c89 * docs: address review feedback on accuracy and scope - quickstart: correct the git/feature note — resolution reads .specify/feature.json / SPECIFY_FEATURE, not the checked-out branch, so switching branches alone does not switch the active feature. - quickstart + agentic-sdd: add an invocation-style note ($speckit-* for Codex/ZCode, /skill:speckit-* for Kimi) so the agent-neutral commands are executable everywhere. - agentic-sdd: fix the tasks phase structure to match the generator (Setup, Foundational, one phase per user story, final Polish; tests optional within user-story phases). - index: soften the catalog claim (catalogs curate discovery, not an install allow-list) and relabel the "CLI reference" link to "Reference" to match the retitled, broader page. Assisted-by: GitHub Copilot (model: Claude Opus 4.8, supervised) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9d9232f8-ece4-4aa6-a9bd-ff8d74ca1c89 * docs: correct feature-resolution override and add bug-command invocation note - quickstart: the previous fix named the wrong override. The active feature *directory* resolves from SPECIFY_FEATURE_DIRECTORY then .specify/feature.json; SPECIFY_FEATURE only supplies the identifier after a directory is resolved. Rewrite the note to point users at .specify/feature.json / SPECIFY_FEATURE_DIRECTORY, and clarify the git extension's branches don't by themselves change the active feature. - agentic-bugfix: add the same invocation-style caveat as the SDD reference ($speckit-bug-* for Codex/ZCode, /skill:speckit-bug-* for Kimi). Assisted-by: GitHub Copilot (model: Claude Opus 4.8, supervised) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9d9232f8-ece4-4aa6-a9bd-ff8d74ca1c89 * docs: tighten bug-command contracts and drop numbered-phase examples - agentic-bugfix: don't overstate overwrite protection — an interactive run can overwrite an existing assessment after confirmation; only automated mode refuses and picks a new slug. Correct the verify verdict to the schema's verified/partial/failed (not-run is a per-check status); an unexercised reproduction downgrades the result to partial. - agentic-sdd: the implement examples labeled scoping "Phase 1/2", but the tasks contract reserves Phase 1 for Setup and Phase 2 for Foundational (user stories start at Phase 3). Scope by phase name and user-story content instead to avoid mis-scoping execution. Assisted-by: GitHub Copilot (model: Claude Opus 4.8, supervised) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9d9232f8-ece4-4aa6-a9bd-ff8d74ca1c89 --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> |
||
|
|
b08d665837 |
docs: document extensions.yml hook configuration (#3563)
* docs: document extesnion.yml hook confriguration * docs: address hook confriguration review feedback * clarify auto execute hooks behavior * docs: clarify hook priority and condition behavior |
||
|
|
aaf6bc22e3 |
docs: refresh landing page ecosystem stats (#3561)
* docs: refresh landing page ecosystem stats Update stale numbers in docs/index.md to match current catalogs on upstream/main and live GitHub data: extensions 105->138, presets 22->25, integrations 30+->35, contributors 200+->240+, friends 4->6, GitHub stars 106K+->121K+, extension authors 60+->70+, and the last-updated date. Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: e5f34221-4e6c-42e8-9fa3-5cfbc26104d1 * docs: align community extension stats on overview page Update docs/community/overview.md from "Over 90 ... 50+ authors" to "Over 130 ... 70+ authors" so it matches the refreshed landing-page numbers in docs/index.md (137 community extensions, 77 unique authors). Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 134166d9-e599-44fa-a88d-daf84ab6aca6 --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> |
||
|
|
c40db8ac10 |
[extension] Add Dotdog extension to community catalog (#3558)
* Add Dotdog extension to community catalog Add dotdog extension submitted by @logohere to: - extensions/catalog.community.json (alphabetical order) - docs/community/extensions.md community extensions table Closes #3555 Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(catalog): alphabetize dotdog entry and correct tool requirement Assisted-by: GitHub Copilot (model: claude-opus-4.8, autonomous) --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> |
||
|
|
29eb6eddf1 |
Update DocGuard — CDD Enforcement to v0.33.0 (#3559)
Update docguard extension submitted by @raccioly: - extensions/catalog.community.json (version, download_url, description, updated_at) - docs/community/extensions.md community extensions table Closes #3556 Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> |
||
|
|
fd101d531e |
feat(integrations): add Grok Build skills-based integration (#3535)
* feat(integrations): add Grok Build skills-based integration Add first-class support for xAI Grok Build via SkillsIntegration, installing speckit skills under .grok/skills and wiring init/invocation/catalog surfaces. Assisted-by: Grok Build (model: grok-4, supervised) * test+docs: address Copilot review on Grok multi-install and next steps Assert init next-steps guidance for Grok (.grok/skills, /speckit-*) and clarify that multi-install safety is path/manifest isolation, not agent-context defaults such as shared AGENTS.md. * fix(integrations): Grok headless --always-approve and isolation paths Document Grok multi-install isolation as .grok/skills and .grok/rules. Override build_exec_args to pass --always-approve so non-interactive dispatch is not blocked at tool permission gates. * docs(integrations): list only managed .grok/skills for Grok isolation Multi-install isolation documents Spec Kit-managed paths; Grok only writes .grok/skills, so drop the read-only .grok/rules entry. * fix(integrations): always-slash Grok hooks and refresh catalog date Move grok to ALWAYS_SLASH_AGENTS so hooks never emit /speckit.plan when ai_skills is missing/false. Update slash-format tests, persist ai_skills on init, and bump catalog updated_at for the Grok entry. --------- Co-authored-by: Nate Chadwick <1232206+natechadwick@users.noreply.github.com> Co-authored-by: test <test@example.com> |
||
|
|
5409670c13 |
[extension] Add Figma Starter extension to community catalog (#3547)
* Add Figma Starter extension to community catalog Add figma-starter extension submitted by @vibhus to: - extensions/catalog.community.json (alphabetical order) - docs/community/extensions.md community extensions table Closes #3545 Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: add missing python3 >=3.8 version constraint in figma-starter catalog entry Assisted-by: GitHub Copilot (model: claude-sonnet-4.5, autonomous) * fix(agent-context): reduce Python subprocess overhead in PS 5.1 YAML fallback The PowerShell update-agent-context.ps1 script falls back to Python when PS 5.1 (which lacks ConvertFrom-Yaml and cannot parse YAML as JSON) reads the extension config. It previously launched Python twice: once to verify that Python 3 + PyYAML were available, and once to run a temp script file that parsed the YAML and printed JSON. On Windows CI each Python startup—plus potential Windows Defender scanning of a freshly-created .tmp file—can take several seconds. With two launches plus PS 5.1's own startup time the subprocess.run(timeout=30) threshold in test_powershell_script_discovers_nested_plan was regularly exceeded, causing the CI job to fail. Replace the two-phase approach with a single Python -c one-liner that verifies PyYAML availability, parses the YAML file, and emits JSON in one process. This halves the number of Python launches and eliminates the temp file entirely, keeping total execution time well under 30 s. Assisted-by: GitHub Copilot (model: claude-sonnet-4.5, autonomous) * revert(agent-context): restore update-agent-context.ps1 to pre-optimization state The runtime optimization (one-liner Python fallback, stderr suppression) was unrelated to the Figma Starter catalog addition (issue #3545) and removed the actionable PyYAML/parse diagnostic messages. Revert the file so the PR only contains the catalog-entry and documentation changes. Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous) --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> |
||
|
|
a4aa4f6701 |
[extension] Add Spec-Kit BDD extension to community catalog (#3548)
* Add Spec-Kit BDD extension to community catalog Add bdd extension submitted by @RSginer to: - extensions/catalog.community.json (alphabetical order) - docs/community/extensions.md community extensions table Closes #3546 Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(catalog): add optional BDD framework tools to bdd extension entry Add the six optional tool dependencies submitted with the bdd extension to the requires.tools field so they appear in CLI extension details: pytest-bdd, behave, @cucumber/cucumber, cucumber, io.cucumber, SpecFlow Assisted-by: GitHub Copilot (model: claude-sonnet-4.5, autonomous) --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> |
||
|
|
7a99c4a230 |
[extension] Update Quality Gates (Enforcement Layer) extension to v0.3.2 (#3542)
* Update Quality Gates (Enforcement Layer) extension to v0.3.2 Update gates extension submitted by @schwichtgit to: - extensions/catalog.community.json (version, download_url, description, provides, changelog, updated_at) - docs/community/extensions.md community extensions table Closes #3541 Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: limit catalog changes to gates entry only, revert unrelated re-serialization Reverts the wholesale Unicode-escape and tools-array reformatting that was unintentionally introduced across catalog.community.json. Only the gates-specific updates remain: - description updated to v0.3.2 wording - version: 0.1.0 → 0.3.2 - download_url updated to gates-0.3.2.zip - git tool required: true → false - commands: 5 → 8, hooks: 1 → 2 - updated_at: 2026-07-13 → 2026-07-15 - changelog field added Assisted-by: GitHub Copilot (model: claude-sonnet-4.5, autonomous) * fix: move changelog field above license in gates entry Move changelog URL field to sit after documentation and before license, matching the catalog's standard field ordering. Remove the dangling changelog at the bottom of the entry so updated_at is the final field with no trailing comma. Assisted-by: GitHub Copilot (model: claude-sonnet-4.5, autonomous) * fix: shorten gates catalog description Assisted-by: GitHub Copilot (model: GPT-5, autonomous) --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> |
||
|
|
6688b447b7 |
feat(workflows): expose workflow source directory to steps (#3469)
* feat(workflows): expose workflow source directory to steps (#3467) Propagate WorkflowDefinition.source_path to steps via {{ context.workflow_dir }} in template expressions and SPECKIT_WORKFLOW_DIR env var for shell steps. The original source directory is persisted in state.json so resume restores the correct value instead of the run-directory copy path. Closes #3467 Assisted-By: 🤖 Claude Code * fix: apply bot review suggestions (#2) Applied fixes from bot review comments: - Comment #3563319058: prevent stale SPECKIT_WORKFLOW_DIR leak from parent env - Comment #3563319094: use cross-platform Python one-liner instead of printenv - Comment #3563319103: add monkeypatch.delenv for deterministic env var test - Comment #3563319116: same env leak fix as #3563319058 Assisted-By: 🤖 Claude Code * fix: use YAML single-quotes and forward-slash paths for Windows CI (#2) sys.executable on Windows returns backslash paths (D:\a\...) which YAML double-quoted strings interpret as escape sequences. Switch to single-quoted YAML strings and normalize paths with replace("\\", "/"). Assisted-By: 🤖 Claude Code * fix: resolve workflow_dir to absolute path and add installed-by-ID test (#3469) Applied fixes from bot review comments: - Comment #3563382853: resolve source_path before taking parent to ensure absolute paths - Comment #3563382864: add test for installed-by-ID workflow_dir semantics Assisted-By: 🤖 Claude Code * docs: document context.workflow_dir and SPECKIT_WORKFLOW_DIR Add reference documentation for the new workflow_dir runtime value in both workflows/README.md and docs/reference/workflows.md so workflow authors can discover the feature and its semantics. Assisted-By: 🤖 Claude Code * fix: clarify installed workflow_dir is an absolute path (#3469) The documentation for context.workflow_dir described the installed-by-ID case as ".specify/workflows/<id>/" which appears relative, contradicting the "resolved absolute path" semantics. Clarified that it is the absolute path to the installation directory. Assisted-By: 🤖 Claude Code * fix: apply bot review suggestions (#3469) Applied fixes from bot review comments: - Comment #3580005128: Quote sys.executable in shell step env var test - Comment #3580005174: Quote sys.executable in no-env-var test Assisted-By: 🤖 Claude Code * fix: apply bot review suggestions (#3469) Applied fixes from bot review comments: - Comment #3587146944: Quote interpolated workflow_dir path in example Assisted-By: 🤖 Claude Code |
||
|
|
ad601e5d52 |
docs: add PyPI as second supported install route (#3425) (#3516)
* docs: add PyPI as second supported install route (#3425) The specify-cli package is now officially published to PyPI via the publish-pypi.yml trusted-publishing workflow. Document PyPI as a supported install route alongside the GitHub source install: - Revise the outdated "not affiliated" warning in installation.md to reflect that specify-cli on PyPI is an official, maintained channel. - Add an "Install from PyPI" section and list PyPI under alternative package managers. - Add a dedicated docs/install/pypi.md guide (install, pin version, verify, upgrade, uninstall). - Add the PyPI guide to the docs TOC. - Mention the PyPI route in the README quick start. Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * docs: refine PyPI install guidance from review (#3516) Address review feedback for the PyPI install documentation: - Reword the verification guidance so `specify version` is described as a local version/runtime check rather than proof of package provenance. - Clarify that upgrading a pinned `uv tool` install to the newest PyPI release requires an unpinned reinstall command. - Note that `specify self upgrade` rebuilds `uv tool` and `pipx` installs from the GitHub source release URL rather than preserving a PyPI-based installation. Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * docs: clarify PyPI verification and upgrade guidance Assisted-by: GitHub Copilot (model: MAI-Code-1-Flash, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * docs: point PyPI provenance check to source metadata Address review feedback: version/list commands do not reveal install provenance. Direct readers to the source metadata their package manager records (pipx list --json, PEP 610 direct_url.json) to confirm whether an install came from PyPI or a Git URL, and note pip show cannot see uv/pipx-managed environments. Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> |
||
|
|
faeb956664 |
Add PatchWarden Evidence Pack extension to community catalog (#3514)
Add patchwarden-evidence extension submitted by @jiezeng2004-design to: - extensions/catalog.community.json (alphabetical order) - docs/community/extensions.md community extensions table Closes #3512 Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> |
||
|
|
99a3b7ccab |
Update Autonomous Run Governance preset to v0.1.4 (#3511)
Update autonomous-run-governance preset submitted by @hindermath to: - presets/catalog.community.json (version, download_url, documentation, provides) - docs/community/presets.md community presets table Closes #3510 Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> |
||
|
|
d7b6626218 |
feat(workflows): align workflow CLI with extension command surface (#3419)
* feat(workflows): align workflow CLI with extension command surface Adds the missing workflow commands and flags so the workflow CLI matches the extension/preset pattern: add --dev and --from, search --author, update, enable and disable. Disabled workflows are blocked from running and marked in list output. Fixes #2342 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(workflows): preserve disabled state on update, guard corrupted registry entries Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(workflows): guard list against corrupted registry entries, re-raise typer.Exit in catalog install workflow list now skips non-dict registry entries with a warning instead of crashing, matching update/enable/disable. The broad except in _install_workflow_from_catalog no longer swallows typer.Exit, so precise errors like the non-HTTPS redirect message are not duplicated. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(workflows): escape rich markup in id-mismatch errors and validate --from source early The two id-mismatch error paths interpolated repr() into Rich markup, so a stray bracket in a user typo could be parsed as markup. Route both through rich.markup.escape. `workflow add <source> --from <url>` also validated the source only after downloading. Validate it up front so a URL/path/typo fails without a network fetch. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(workflows): escape rich markup in list output and catalog install errors, isolate update failures workflow list now escapes id/name/version/description before printing, matching how extensions render user-editable fields. The catalog install helper computes safe_wf_id once and uses it for every early error path plus the final failure message. workflow update wraps _safe_workflow_id_dir and the backup read inside the try/except typer.Exit block so an unsafe id in a corrupted registry fails that one workflow and the rest continue. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(workflows): escape rich markup in --from download exception message Matches how the catalog install path escapes exception strings. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(workflows): catch OSError in per-workflow update loop and make restore best-effort Transient FS errors (perms, disk full) from backup read or write no longer abort the whole update run. The restore is wrapped in its own try/except so a failed write only warns, and the offending workflow is reported via 'Failed to update' like other per-workflow failures. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(workflows): escape rich markup in search output workflow search now escapes catalog-derived name/id/version/description/ tags before printing, matching extension search and workflow list. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: escape workflow validation errors before Rich output Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(workflows): escape remaining unescaped Rich markup paths Covers the last few review threads not yet addressed: - Escape yaml.YAMLError text in the local workflow add install path (matches the already-escaped download/catalog paths). - Escape the non---dev local directory fallback's "No workflow.yml found in <path>" message (the --dev branch already escaped it). - Escape the redirected final_url in the --from non-HTTPS redirect error (IPv6 literals like http://[::1]/... are legal and contain brackets). - Escape the "Downloaded workflow is invalid" exception message in _install_workflow_from_catalog, matching the sibling catalog-install exception handler a few lines above it. Adds regression tests for each in TestWorkflowCliAlignment, following the existing escaping-test pattern in this class. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(workflows): escape workflow name/id in install success messages Workflow names and ids come from user-controlled YAML or external catalog data; printing them unescaped lets bracket characters be interpreted as Rich tags. Escape them in the add/catalog-install success messages and the remaining catalog error paths, matching the rest of the output hardening. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(workflows): fail cleanly on unparseable catalog install URLs urlparse raises ValueError on e.g. an unbalanced IPv6 literal before the invalid-URL branch is reached; on workflow update that also bypassed the per-workflow handler and aborted the whole command. Convert the parse failure into a clean error so add fails cleanly and update skips just the affected workflow. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(workflows): reject catalog updates whose downloaded version mismatches The update path never verified the downloaded workflow carries the catalog version that triggered the update, so a stale or misconfigured URL could report success while leaving the old version installed or downgrading it. Pass the expected version into the install helper and fail the update when the downloaded definition does not match. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(workflows): validate workflow ID in run command and document new CLI flags Path-equivalent spellings like "align-wf/" previously bypassed the registry disabled check because the engine normalizes the path while the registry matches the raw string. workflow run now validates non-file sources against the workflow ID pattern before lookup. Also updates docs/reference/workflows.md with --dev/--from install options, update/enable/disable commands, and the search --author flag. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(workflows): enforce disabled state for direct paths to installed workflows Running the installed copy's YAML directly (specify workflow run .specify/workflows/align-wf/workflow.yml) skipped the registry check. File sources resolving inside .specify/workflows/<id>/ now map back to the workflow ID and refuse to run while disabled. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(workflows): reject explicit empty --from URL instead of catalog fallback 'workflow add foo --from ""' fell through 'from_url or ...' to a catalog install. Distinguish None from empty string so explicit values stay on the URL-validation path and fail closed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(workflows): registry rollback on save failure, consistent disabled check, honest update summary - WorkflowRegistry.add now rolls back its in-memory mutation when save() raises, so a later successful save cannot persist metadata for a failed update alongside the restored YAML backup. - workflow run uses the same truthiness check for 'enabled' as list and disable, so malformed values like 0 or null refuse to run. - workflow update reports 'No workflows were eligible for update' when every target was skipped instead of claiming all are up to date. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(workflows): guard non-string catalog URL and keep enable/disable rollback intact - A truthy non-string catalog url (e.g. 123) reached urlparse and raised AttributeError, escaping the clean error path; validate it is a string. - enable/disable mutated the live registry entry before add(), so add's rollback snapshot captured the already-toggled object; pass a fresh mapping instead. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(workflows): tolerate non-dict registry entries in add and clarify test docstrings A corrupted-but-parseable registry entry (e.g. a string value) crashed WorkflowRegistry.add with AttributeError on existing.get. Guard the non-dict case while still restoring the original raw value on rollback. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(workflows): atomic registry save and accurate mixed-target update summary - save() wrote the registry with open('w'), so a failed dump truncated the file and the next load reset every entry. Write to a sibling temp file and os.replace into place. - workflow update no longer claims all workflows are up to date when some targets were skipped; it reports checked-only status with a skipped count. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(workflows): exclusive temp file for registry save and cwd-independent disabled guard - save() now uses tempfile.mkstemp in the workflows dir (matching the engine's atomic writer), so a pre-created symlink at a predictable .tmp path cannot redirect the write and concurrent processes cannot collide. - The direct-path disabled guard derives the owning project from the resolved file path instead of the caller's cwd, so running an installed workflow's YAML from outside the project still refuses when disabled. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(workflows): symlink guards and shape validation in workflow registry, dev-dir file check - WorkflowRegistry now mirrors StepRegistry: _load refuses symlinked parents/registry file and normalizes a non-dict workflows field; save() rejects symlinked paths before writing. - workflow add --dev requires workflow.yml to be a regular file so a directory named workflow.yml gets the documented CLI error instead of an uncaught IsADirectoryError. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(workflows): validate download redirects before following them All three workflow download sites (add --from, catalog install, step install) passed no redirect_validator to open_url, so an HTTPS URL redirecting to cleartext HTTP issued the insecure request before the post-hoc geturl() check reported it. Shared validator now rejects non-HTTPS redirects (loopback HTTP allowed) pre-follow, matching the preset download path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test(workflows): accept redirect_validator kwarg in step-add open_url fakes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(workflows): guard directory-shaped workflow.yml and unreadable registry - workflow add's plain local-path fallback (no --dev) checked wf_file.exists() before installing, so a directory literally named workflow.yml passed the guard and _validate_and_install_local() leaked an uncaught IsADirectoryError instead of the documented CLI error. Use is_file(), matching the --dev branch's existing guard. - WorkflowRegistry._load() treated any OSError while reading an existing registry the same as corrupted JSON, resetting to an empty in-memory registry. A later save() would then silently persist that empty state via os.replace, discarding every previously installed workflow entry. Track a _load_error flag on OSError-during-read and have save() refuse to write when it is set, so a transient I/O failure can no longer overwrite intact data on disk. - docs/reference/workflows.md: document `--from <url>` with its value placeholder, matching extensions.md and presets.md. Assisted-by: GitHub Copilot (model: Claude Sonnet 5, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(workflow): rollback registry.remove() and guard OSError at CLI boundaries Critical: WorkflowRegistry.remove() deleted the in-memory entry then called save() with no rollback, unlike add(). Combined with workflow_remove deleting the workflow directory before calling registry.remove(), a save failure permanently destroyed the workflow's files, left the on-disk registry still claiming it installed, and surfaced a raw unhandled OSError with no CLI message. - WorkflowRegistry.remove() now rolls back the in-memory entry on a save() OSError, mirroring add()'s existing rollback pattern. - workflow_remove persists the registry removal (registry.remove(), wrapped in try/except OSError -> clean escaped message) before deleting any files, so a save failure never touches the workflow directory. Important sibling paths: workflow add (local/--dev/--from and catalog), enable, and disable all called registry.add() without catching its deliberate OSError, so a save failure surfaced either an orphaned install directory (fresh local/catalog installs) or a raw/unhandled exception with no clean CLI output. - _validate_and_install_local (backs local/--dev/--from) now removes the freshly created directory on a fresh install, or restores the prior workflow.yml bytes on a reinstall-over-existing-local install, before raising a clean escaped error. - _install_workflow_from_catalog wraps the final registry.add() using the function's own established convention (rmtree the just-downloaded workflow_dir, then a clean escaped error) -- workflow_update's existing backup/restore around this function is unaffected. - workflow_enable/workflow_disable catch registry.add()'s OSError and print a clean escaped message instead of leaking the exception. Added failing-first tests proving each behavior (registry-unit rollback test, CLI-level remove/add/enable/disable save-failure tests parametrized where they share one root cause), all confirmed red before the fix and green after. Assisted-by: GitHub Copilot (model: Claude Sonnet 5, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(workflow): preserve prior catalog install on reinstall registry-save failure _install_workflow_from_catalog's final registry.add() failure handler unconditionally rmtree'd workflow_dir. That's safe for a brand-new install, but plain `workflow add <catalog-id>` also allows re-adding an already-installed workflow, downloading the new version over the existing directory first. If registry.add() then failed to save, the unconditional rmtree deleted the prior working install while the registry (after its own rollback) still reported it installed -- data loss with no way back. workflow_update already avoids this via an outer backup/restore around this function, but plain add has no such caller. Fix mirrors _validate_and_install_local's existed-before/backup-aware handling: capture whether workflow_dir existed and back up its workflow.yml bytes before any download write, then on a registry.add() OSError, restore those bytes for a reinstall or rmtree only a brand-new directory. Only one file (workflow.yml) is ever written by this path, so no further per-file bookkeeping is needed. Added a failing-first regression: install a catalog workflow, re-add it with a simulated registry save OSError, and assert a clean error, the original workflow.yml restored byte-for-byte, and the registry still reporting the original version installed. Confirmed red (prior file deleted) before the fix, green after. Assisted-by: GitHub Copilot (model: Claude Sonnet 5, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(workflow): centralize catalog-install cleanup across all failure branches _install_workflow_from_catalog is new in this PR and has seven failure branches after the mkdir/download step, each independently rmtree'ing workflow_dir: redirect-to-non-HTTPS rejection, a generic download exception, invalid downloaded YAML, a validate_workflow failure, a workflow-id/catalog-key mismatch, a version mismatch, and (fixed in the prior commit) a registry.add() OSError. Only the last one had been special-cased to spare a prior working install on reinstall; the other six still unconditionally deleted the whole directory, so re-adding an already-installed catalog workflow and hitting any of those six earlier failures destroyed the working install even though nothing about it had actually changed. Replaced all seven ad hoc rmtree call sites with a single local _cleanup_failed_install() helper that closes over the existed_before / prior_workflow_bytes captured once at the top of the function: restore the prior workflow.yml for a reinstall, or rmtree only a directory that this attempt itself created. Every failure branch now calls this one helper, so the fix is structural rather than duplicated, and every existing error message/exit code is unchanged -- only the cleanup performed before each message is different. Added a parametrized regression test covering the four early-failure trigger points reachable from plain workflow add (redirect rejection, download exception, invalid YAML, ID mismatch): each installs a catalog workflow, re-adds it while forcing that specific failure, and asserts a clean error plus the original workflow.yml surviving byte-for-byte. Confirmed red against the unfixed code (all four raised FileNotFoundError reading the deleted file) before applying the helper, green after. Assisted-by: GitHub Copilot (model: Claude Sonnet 5, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(workflow): restore registry entry verbatim on post-removal rmtree failure workflow_remove now persists registry.remove() before deleting any files (fixed previously), but if the registry write succeeds and the subsequent shutil.rmtree(workflow_dir) then fails, the registry was left claiming the workflow uninstalled while its directory remained on disk -- an orphaned install with no path back to a clean state. workflow_step_remove already handles this exact sequencing by capturing the registry entry before removal and restoring it directly into registry.data plus save() (bypassing add(), which would stamp a new updated_at) if the directory removal fails afterwards. Applied the same pattern to workflow_remove: capture registry_metadata via registry.get() before registry.remove(), and on an rmtree OSError, write it straight back into registry.data["workflows"][workflow_id] and save(), matching workflow_step_remove's restore-failure handling (a yellow warning, not a hard failure, since the primary error is already about to be reported). Existing error message and exit behavior for the rmtree failure are unchanged. Added a failing-first regression: install a workflow, monkeypatch shutil.rmtree to raise OSError, and assert a clean existing error message, the directory remaining (rmtree never actually deleted anything), and the registry entry restored byte-for-byte identical (including installed_at/updated_at) -- proving the fix bypasses add() and doesn't re-stamp timestamps. Confirmed red (registry entry stayed None) before the fix, green after. Assisted-by: GitHub Copilot (model: Claude Sonnet 5, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix 4 current Copilot review findings on workflow run/registry/install 1. workflow run ownership check followed symlinks via Path.resolve() before mapping a direct YAML path back to its installed workflow ID. A symlinked .specify/workflows/<id>/workflow.yml resolved outside the tree, missed the ownership match entirely, and let the disabled-workflow guard be silently skipped while engine.load_workflow still followed the symlink. Now maps ownership from a lexically-normalized path (os.path. normpath, no symlink following) and explicitly refuses to run if the installed <id> directory or workflow.yml leaf is itself a symlink. Direct external workflow paths that don't match .specify/workflows/... are unaffected. 2. WorkflowRegistry._load() caught a read OSError and silently fell back to an empty in-memory registry, only blocking a later save(). Callers that only query is_installed()/get()/list() before writing a file (e.g. commands/init.py's bundled speckit install, which overwrites workflow.yml once is_installed() reports false) could act on that false-empty state and destroy real data before ever reaching save(). _load() now raises OSError immediately so an unreadable registry fails closed at construction, before any query or side effect is possible. Added _open_workflow_registry() to give every CLI command a consistent clean-error boundary around registry construction. 3. _validate_and_install_local's mkdir/copy2 ran before the try/except that protected registry.add(); a copy2 failure (e.g. a truncating partial write on a reinstall) was not caught at all, so the existing backup-restore cleanup never ran and the prior working workflow.yml was corrupted with a raw traceback surfaced to the user. mkdir/copy2 now run inside the same rollback-protected section as registry.add(), sharing one _cleanup_failed_install() helper. 4. workflow update's skip message claimed any non-catalog source was installed "from a local path or URL", which is wrong for the bundled speckit workflow (source: "bundled"). Message is now source-neutral. Verified all 4 threads are current (not outdated) via GraphQL review thread query on PR #3419, HEAD 812050a. Tests: strict TDD per fix (red test proving each bug, minimal production change, green). tests/test_workflows.py: 474 passed. Full suite: 3976 passed, 110 skipped. ruff check: all checks passed on touched files and full src tree. Assisted-by: GitHub Copilot (model: Claude Sonnet 5, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix disabled-workflow bypass via symlinked .specify project root workflow run's ownership check derived registry_root/registered_id from the lexical path, then checked the id directory and workflow.yml leaf for symlinks -- but never checked .specify or .specify/workflows themselves for that derived root. _reject_unsafe_workflow_storage only guards the cwd's project_root, which can differ from the path-derived registry_root (a direct path into an unrelated project, or that project's own .specify being a symlink to an attacker-controlled tree). WorkflowRegistry's own symlinked-parent handling silently substitutes an empty registry instead of raising, so a query against it (is_installed/get returning "not found") is not a safety signal a caller can rely on: with a symlinked .specify, the disabled check saw no registry entry and let a disabled workflow run anyway. Fix: reject an unsafe .specify/.specify-workflows for the actual derived registry_root before ever consulting the registry, reusing the existing _reject_unsafe_dir helper already used by _reject_unsafe_workflow_storage. Red-first end-to-end repro: victim project's .specify symlinked to an attacker-controlled tree containing a disabled workflow entry, run invoked with a direct path from an unrelated cwd -- confirmed the disabled workflow executed (exit 0) before the fix, now refused cleanly. Tests: tests/test_workflows.py 475 passed. Full suite: 3977 passed, 110 skipped. ruff check: all checks passed. Assisted-by: GitHub Copilot (model: Claude Sonnet 5, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix raw exception leak in bundle remove primitive boundary remove_bundle() had no exception handling around its component removal loop, unlike install_bundle() which converts any raw exception into a clean BundlerError. Since WorkflowRegistry now fails closed (raises OSError) on an unreadable registry file, and _WorkflowKindManager.__init__ constructs WorkflowRegistry with no try/except, an unreadable workflow registry surfaced as a raw OSError through remove_bundle(). The bundle_remove CLI command only catches BundlerError, so the raw OSError propagated uncaught, producing exit_code=1 with empty output instead of a clean, actionable message. Wrap remove_bundle()'s component loop in the same try/except BundlerError: raise / except Exception: raise BundlerError(...) from exc pattern already used by install_bundle(), converting any raw exception at this shared boundary. save_records() remains outside the try block, so a failure still leaves the bundle's record untouched (no removal side effects recorded). Tests: - tests/integration/test_bundler_install_flow.py::test_remove_converts_raw_installer_exception_to_bundler_error (function-level regression: a raw OSError from installer.is_installed must become a clean BundlerError, and the bundle record must survive) - tests/contract/test_bundle_cli.py::test_remove_reports_clean_error_when_primitive_raises_raw_exception (CLI-level regression: `specify bundle remove` must print a clean actionable message and exit non-zero instead of raw/empty output) Both tests were confirmed red beforehand: the raw OSError propagated uncaught out of remove_bundle(), and the CLI-level CliRunner result showed exit_code=1 with empty output. Assisted-by: GitHub Copilot (model: Claude Sonnet 5, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix 8 current Copilot review findings on registry fail-closed, rollback orphans, backup-read boundaries, and Rich escaping 1. WorkflowRegistry._load(): a symlinked .specify/.specify/workflows parent (or a symlinked registry file) silently returned an empty registry instead of raising, unlike an unreadable-file read failure. A read-only caller (notably the bundler's remove path) querying is_installed() before ever writing could conclude an installed workflow is absent, skip removing it, then delete the bundle record -- leaving the workflow untracked but still on disk. Now raises OSError immediately, matching the existing unreadable-file fail-closed behavior. 2/8. _validate_and_install_local and _install_workflow_from_catalog: when the destination directory already existed but had no prior workflow.yml (e.g. a leftover empty dir), existed_before was True but there were no backup bytes to restore, so the rollback closure did nothing on a later failure -- leaving the newly copied/ downloaded file behind. Both now unlink the newly created file in this case, restoring the pre-existing directory to its prior (empty) state. 3/4. Both install paths read the prior workflow.yml bytes (to seed the reinstall rollback) *before* any try/except boundary: a read failure on the existing file (e.g. a transient permission/FS issue) leaked a raw, unescaped OSError instead of the same clean CLI error used by every other failure branch in these functions. Both reads are now guarded by their own try/except OSError, with no writes attempted before the read succeeds (so there is nothing to roll back on this specific failure). 5. remove_bundle's exception-conversion message unconditionally claimed "No changes were recorded," even though a failure can occur after earlier components in the same bundle have already been removed from disk (save_records never runs on this path, so the record is left claiming the bundle fully installed). The message now reports how many components were already removed when that happened, instead of asserting no changes occurred. 6/7. workflow_remove's new post-registry-removal directory-failure error and its restore-failure warning interpolated workflow_dir and the exception values into Rich markup unescaped. A project path or OS error message containing Rich-markup-like brackets could be parsed as markup and hide/corrupt the displayed text. Both now use the existing _escape_markup helper, consistent with every other error path in this file. Tests (tests/test_workflows.py unless noted): - TestWorkflowRegistry::test_load_symlinked_workflows_dir_fails_closed_not_silently_empty (1) - TestWorkflowCliAlignment::test_add_dev_fresh_install_into_preexisting_empty_dir_cleans_new_file (2) - TestWorkflowCliAlignment::test_add_catalog_fresh_install_into_preexisting_empty_dir_cleans_new_file (8) - TestWorkflowCliAlignment::test_add_dev_reinstall_backup_read_failure_gives_clean_error (3) - TestWorkflowCliAlignment::test_add_catalog_reinstall_backup_read_failure_gives_clean_error (4) - tests/integration/test_bundler_install_flow.py::test_remove_partial_failure_message_reflects_partial_state (5) - TestWorkflowRemoveGuard::test_remove_directory_and_restore_failure_escapes_rich_markup (6/7) All seven were confirmed red beforehand, matching each thread's described failure mode exactly (silent empty registry instead of a raise; orphaned new file left behind; raw unescaped OSError leaking; a misleading "no changes were recorded" claim; Rich markup consuming bracketed path/exception text). Also updated test_registry_save_refuses_symlinked_parent, a pre-existing test that asserted the symlinked-parent raise at add()/save() time -- it now raises at construction instead, per fix #1, so the test was adjusted to match without weakening its guarantee (still asserts no writes occur under the symlinked target). Assisted-by: GitHub Copilot (model: Claude Sonnet 5, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix 3 current Copilot review findings: bookkeeping-aware BundlerError removal, bounded workflow downloads 1. bundle remove: BundlerError raised by the primitive installer itself (e.g. from a kind manager) bypassed the partial-removal bookkeeping message added previously via a bare `except BundlerError: raise`. Now routes through the same detail-construction logic as generic exceptions, so a mid-loop BundlerError after an earlier successful removal still reports that the project may be partially uninstalled, while a zero-removal BundlerError still reports "No components were removed." Both preserve the original exception message and chain `from exc`. 2/3. workflow add --from and catalog install/update downloads used unbounded `response.read()`, buffering the entire server-controlled body into memory before any size check, and trusted Content-Length alone where checked at all. Added a single shared `_read_response_within_limit()` helper reused by both call sites: it fails fast on an oversized declared Content-Length, and separately enforces the same cap while streaming in 64KiB chunks so a chunked or Content-Length-less response cannot bypass the limit by lying about or omitting its size. Chose 5 MiB as the cap: workflow YAML definitions are small step/metadata text, not binaries, so this is generous headroom against a malicious/misbehaving server without affecting any legitimate workflow definition. Both call sites already route any raised exception through their existing clean-error and rollback (`_cleanup_failed_install`) paths, so no additional error-handling plumbing was needed. Tests: extended the shared `_FakeResponse` test helper (and 5 duplicate per-test FakeResponse classes) to support `.read(amt)` chunked reads with an internal cursor (backward compatible with existing bare `.read()` callers) plus header simulation. Added red-first tests for: BundlerError after partial removal reporting partial state, BundlerError with zero removals reporting no changes, --from oversized-Content-Length rejection, --from oversized-streamed-body-without-Content-Length rejection, and the same two cases for the catalog install path (asserting no orphan directory/registry mutation on rejection). tests/integration/test_bundler_install_flow.py: 17 passed tests/test_workflows.py: 485 passed tests -q: 3992 passed, 110 skipped ruff check: clean on all touched files Assisted-by: GitHub Copilot (model: Claude Sonnet 5, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix temp-file leak in workflow add --from and strengthen size-limit test assertions workflow_add's --from download path opened a NamedTemporaryFile(delete=False) -- which creates the file on disk immediately -- then wrote the size-limited response body before assigning `tmp_path`. If `_read_response_within_limit` raised (oversized declared Content-Length, or an over-cap streamed body with no/understated Content-Length), the exception propagated out of the `with` block before `tmp_path` was ever set, so the outer except handler had no path to clean up: a 0-byte `.yml` temp file was left behind permanently on every rejected/failed --from download. Fixed by assigning `tmp_path` immediately after the file is opened (before the size-limited read/write), and unlinking it in the except branch when set. Normal post-download cleanup in the existing `finally: tmp_path.unlink(missing_ok=True)` is unchanged. Verified (not assumed) the catalog install path has no equivalent leak: it writes the response bytes directly to `workflow_file` inside `workflow_dir` (no separate temp file), and any read/size-limit failure is already caught by the existing `except Exception: _cleanup_failed_install()` handler, which correctly restores a reinstalled file or removes a freshly-created directory. While investigating, found the previous round's 4 size-limit tests were false positives: `_read_response_within_limit`'s `max_bytes` parameter had its default bound to `_MAX_WORKFLOW_YAML_BYTES` at function-definition time, so monkeypatching the module attribute in tests had no effect on the function's actual behavior -- the tests were passing because the oversized mock bodies failed downstream YAML/id validation instead of the size check. Fixed by resolving `max_bytes` from the module attribute at call time (default `None`, resolved inside the function body) so tests can actually override the effective limit, and strengthened all 4 tests' assertions to match the specific size-limit error text (whitespace-collapsed to tolerate Rich's line-wrapping), so they now prove the real code path fires. Tests: added 2 red-first regression tests (oversized-streamed-body and oversized-Content-Length --from downloads leave no leftover temp file, verified against a scratch tempfile.tempdir), confirmed red (real 0-byte file found) before the fix and green after. Strengthened the pre-existing 4 --from/catalog size-limit tests to assert on the actual error message instead of generic exit-code/non-empty-output checks. tests/test_workflows.py: 487 passed tests -k bundler: 186 passed tests -q: 3994 passed, 110 skipped ruff check: clean on all touched files Assisted-by: GitHub Copilot (model: Claude Sonnet 5, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Harden workflow install/remove transactions with atomic staging Addresses 5 Copilot review findings on HEAD b8269c8, all centered on transaction integrity around workflow install/remove/registry writes, following the atomic_write_json pattern already used in _utils.py: 1. WorkflowRegistry.save() now preserves the existing registry file's mode (e.g. 0640/0644) across a save instead of silently downgrading it to mkstemp's 0600 default; a brand-new registry still gets the secure 0600 default. 2. workflow_remove now stages the install directory out of the way via an atomic rename *before* the registry write, rather than deleting it directly with shutil.rmtree after the registry already claims it removed. This closes a real data-integrity gap: a partially-failed rmtree could no longer leave a damaged directory re-marked "installed" by the old manual restore-after-rmtree-failure code (now deleted -- it's structurally impossible to need it). A registry-write failure renames the staged directory back (guarded, with an explicit warning if the restore-back rename itself fails); a registry-write success is durable, so a later failure to delete the staged directory is now a warning (exit 0), not a contradictory "Error: Failed to remove" (exit 1) that used to claim failure while the registry already recorded success. 3. Local (--dev/--from/plain path) and catalog install/reinstall now write new content to a same-directory staging file and commit it onto the destination workflow.yml via a single atomic swap, instead of writing/downloading directly into the destination file. A prior file (reinstall) is renamed aside rather than overwritten in place, so it can be restored via rename -- never a content rewrite -- if registry.add() subsequently fails; a rollback failure is now explicitly reported as a warning instead of escaping unguarded and masking the original clean error. This also removes the need to read the prior file's bytes into memory before installing (that read-before-write step and its failure mode are now unreachable), and both local and catalog installs share the same four small helpers (_stage_workflow_file / _commit_workflow_file / _discard_staged_workflow_file / _rollback_committed_workflow_file, plus guarded wrappers) rather than duplicating the logic. 4. Updated a stale comment (workflow_run's ownership-guard rationale) that still described WorkflowRegistry._load() as silently substituting an empty registry; it now fails closed by raising OSError, which the comment now states plainly. Tests: rewrote the two workflow_remove tests whose assertions encoded the old (incoherent) rmtree-then-restore contract to instead prove the new stage-then-commit contract (post-registry-success cleanup failure is a warning+exit 0; pre-registry-success stage-restore failure is guarded and escapes markup correctly). Rewrote the local/catalog "backup read failure" tests, which tested a step the new design no longer performs, into "restore-rename failure" tests proving the new guarded rollback boundary. Added registry file-mode preservation tests. All other existing install/remove/reinstall tests (save-failure cleanup, pre-existing-empty-dir handling, early-failure-during- reinstall parametrized cases, Rich markup escaping) continue to pass unmodified against the new implementation. Verified via GraphQL that all 5 threads are current (not outdated/ resolved) before fixing. Full suite: 3996 passed, 110 skipped. Ruff clean on all touched files. Assisted-by: GitHub Copilot (model: Claude Sonnet 5, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Discard reinstall backup file after registry.add() succeeds _commit_workflow_file() renames a prior workflow.yml aside to workflow.yml.bak so it can be restored if registry.add() subsequently fails. Neither the local install/reinstall path nor the catalog install/reinstall path ever cleaned up that backup after a successful registry.add() -- every successful reinstall permanently left a workflow.yml.bak sibling, which later reinstalls would silently overwrite/re-orphan. Add a shared _discard_committed_backup_file() helper, called from both success paths right after registry.add() durably succeeds (and before the final "installed" message, preserving output ordering). A fresh install (backup_file is None) is a no-op. A cleanup failure is reported as a warning (exit 0), not a failure, since the install itself already succeeded -- consistent with workflow_remove's post-commit cleanup warning semantics. Add red-first regression tests proving: (1) successful local reinstall leaves no workflow.yml.bak sibling, (2) successful catalog reinstall leaves no workflow.yml.bak sibling, (3) a cleanup failure on the backup file after a successful reinstall reports a warning and still exits 0 with the registry correctly reflecting the new install. Assisted-by: GitHub Copilot (model: Claude Sonnet 5, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Clean up freshly-created dest_dir when staging mkstemp fails _stage_workflow_file() does dest_dir.mkdir(parents=True, exist_ok=True) then tempfile.mkstemp(dir=dest_dir, ...). For a fresh install (no prior directory), if mkdir succeeds but mkstemp then raises (disk full/EMFILE/quota), the exception previously propagated straight past both the local-install and catalog-install call sites without any cleanup, leaving the newly-created empty workflow directory orphaned on disk with no error indicating why. Fix at the shared _stage_workflow_file() boundary instead of duplicating cleanup at each call site: track whether this call created dest_dir: on a mkstemp failure, remove that directory via a guarded rmdir (never a broad rmtree, so any concurrently written content would be left untouched) before re-raising the original OSError unchanged. A pre-existing (reinstall) dest_dir is never touched by this cleanup, and a cleanup failure is reported as its own warning without masking the original error. Add red-first regression tests proving: a fresh local install (--dev, plain local path, --from) and a fresh catalog install both clean up the orphaned directory on a simulated mkstemp failure, and a reinstall over a pre-existing directory is left untouched by the same failure. Assisted-by: GitHub Copilot (model: Claude Sonnet 5, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix installed-workflow ownership/disabled bypass and resume enforcement Address 3 current Copilot review findings on the disabled-workflow guard in `workflow run`/`workflow resume`: - The lexical `.specify/workflows/<id>` ownership scan stopped at the first match scanning from the start of the path. A nested project living beneath an outer installed workflow's own directory tree (reusing the same segment names) was attributed to the wrong (outer) workflow and ID, gating the run on an unrelated workflow's disabled state. `_scan_for_workflow_owner` now scans from the end so the nearest (innermost) owner always wins. - A path with no `.specify/workflows` segments of its own (e.g. `/tmp/alias.yml`) that is itself a symlink resolving *into* installed storage bypassed the disabled check entirely, since only the raw lexical path was inspected. `_resolve_installed_workflow_ownership` now additionally resolves the real path when the lexical scan finds no owner and re-runs the same scan against it, so an outward-pointing alias into a disabled workflow is caught too. Genuinely standalone external files (no symlink anywhere on the path) are unaffected. - `workflow resume` bypassed the disabled check altogether: engine.resume() replays a persisted run directly from disk with no registry awareness. RunState now optionally persists `installed_workflow_id` and `installed_registry_root` at run start (set by workflow_run when the source resolved to an installed ID); `workflow_resume` pre-loads the run state and re-checks the registry's *current* disabled state before calling engine.resume(), mirroring workflow_run's own guard. Both new fields default to None via RunState.load()'s `.get()`, so runs from a direct/non-installed source, and any run persisted before this schema addition, resume exactly as before. The ownership-mapping logic (previously inlined in workflow_run) is extracted into `_resolve_installed_workflow_ownership` / `_scan_for_workflow_owner` so both the lexical and resolved-path cases share the same scan and the existing inward-symlink-component refusal. Assisted-by: GitHub Copilot (model: Claude Sonnet 5, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Guard --from temp cleanup; drop redundant update rollback; mark POSIX-only tests Two more current Copilot review findings, both in workflow_add/update: - `workflow add --from`'s `finally: tmp_path.unlink(missing_ok=True)` ran unguarded after `_validate_and_install_local` had already committed the file and registry entry (success) or already raised its own clean `typer.Exit` (failure). An OSError from that cleanup unlink would surface as an unhandled failure even though the install itself succeeded. It is now wrapped in try/except OSError, printing a neutral warning that doesn't claim success or failure (the finally runs on both outcomes) instead of propagating. - `workflow_update`'s per-item loop performed its own outer backup (`wf_file.read_bytes()`) and restore (`wf_file.write_bytes(backup)`) around `_install_workflow_from_catalog`, which is itself fully transactional (staged download, atomic rename-based commit, its own rollback on registry failure) and never leaves a raw OSError or a partially-written workflow.yml. The outer restore was therefore dead weight for its stated purpose, and — being an unguarded byte-level write — was itself an unnecessary place a second failure could truncate an already-safely-preserved file. Removed; the loop now only records success/failure. Also marks 3 registry-save file-mode tests (`test_registry_save_preserves_existing_file_mode`, `test_registry_save_on_new_registry_uses_secure_default_mode`, `test_registry_save_failure_preserves_file_on_disk`) as POSIX-only via the repo's existing `skipif(sys.platform == "win32", ...)` pattern, since they assert exact POSIX permission bits that don't hold on Windows. Assisted-by: GitHub Copilot (model: Claude Sonnet 5, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Report possible partial changes on zero-removed bundle removal failure The final Copilot review finding: `remove_bundle`'s zero-removed-components error message claimed "No components were removed." even when the failing installer component may have deleted files before raising -- prior review rounds already established that DefaultPrimitiveInstaller's removal paths are not atomic and can leave partial filesystem changes despite raising before `result.uninstalled` is populated. The zero-count message is now a conservative caution ("...but the failing component may have made partial changes before raising, so the project may be partially uninstalled.") instead of an unconditional claim of no side effects. The >0-removed path (which already reports the confirmed partial list) is unchanged. Assisted-by: GitHub Copilot (model: Claude Sonnet 5, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix workflow resume disabled-check bypass after project move/rename RunState.installed_registry_root previously persisted the creation-time absolute project path unconditionally whenever a run belonged to an installed workflow. After the whole project directory was renamed or moved, workflow_resume would open a WorkflowRegistry at that now nonexistent path, get back an empty/default registry, and silently skip the disabled-workflow check -- a paused run for a disabled workflow could be resumed successfully from the new location. Fix persists installed_registry_root only when the owning root genuinely differs from the current project_root (true cross-project direct-file- source invocations). The common same-project case now persists None and is re-derived from the live project_root at resume time via a new _resolve_run_owner_root() helper, which also falls back to project_root if a stored root no longer exists on disk -- covering both the common case transparently surviving project moves and the cross-project case degrading safely if its owner project vanishes, rather than silently skipping the disabled check. Backward compatible: state files missing the new fields, and states with a still-existing distinct cross-project root, behave unchanged. Added regression tests: - resume blocked after project moved then disabled at new location - resume still works after project moved while workflow stays enabled - cross-project registry root is still correctly honored when it exists - resume falls back to current project's registry when a stored cross-project root no longer exists Assisted-by: GitHub Copilot (model: Claude Sonnet 5, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix silenced cleanup failures and malformed run-state type validation Four fixes from Copilot review on HEAD 4f24735: 1. _discard_staged_workflow_file's fresh-install directory removal used shutil.rmtree(dest_dir, ignore_errors=True), so a genuine cleanup failure there could never reach _safe_discard_staged_workflow_file's warning -- an orphaned directory was left behind with zero report. Now removes only if dest_dir still exists and lets a real OSError propagate to the existing safe wrapper, which warns while the already-printed original install error remains primary. 2. _rollback_committed_workflow_file's fresh-install directory removal (post registry.add() failure) had the same ignore_errors=True gap; fixed identically so _safe_rollback_committed_workflow_file's warning can actually fire. 3. In the --from download-failure branch, tmp_path.unlink(missing_ok= True) was unguarded: if it raised (e.g. read-only tempdir), it replaced the original "Failed to download workflow" error with a raw unhandled OSError instead of a clean typer.Exit. Now guarded exactly like the later post-install finally cleanup: a cleanup failure prints a warning and the original download error is still reported cleanly. 4. RunState.load() trusted installed_workflow_id/installed_registry_root straight out of state.json with no type validation. A malformed value (int/list/dict/bool instead of str-or-null) would crash deep inside _resolve_run_owner_root or the registry lookup (TypeError building a Path, unhashable dict/list as a mapping key) instead of failing cleanly. Both fields are now validated as str | None during load, raising a clear ValueError that workflow_resume's existing ValueError boundary already converts into a clean CLI error with no traceback. Valid values (including the empty-string fallback already handled by _resolve_run_owner_root) continue to load unchanged. Added red-first regression tests for each: staged-discard cleanup warning, rollback cleanup warning, download-failure cleanup-vs-original- error precedence, and parameterized malformed/valid run-state field coverage. Assisted-by: GitHub Copilot (model: Claude Sonnet 5, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add ValueError boundary to workflow status single-run lookup QUALITY re-review flagged that fa410d3's new RunState.load() type validation (malformed installed_workflow_id/installed_registry_root raising ValueError) leaked as a raw unhandled traceback through `workflow status <run_id>`, which only caught FileNotFoundError. `workflow resume` already had the matching ValueError boundary. Adds an `except ValueError as exc: console.print(f"[red]Error:[/red] {exc}"); raise typer.Exit(1)` clause mirroring resume's exact pattern (unescaped interpolation, consistent with the existing convention at every other ValueError boundary in this file). FileNotFoundError behavior and the no-run-id list-all-runs path are unchanged. Added parametrized regression covering malformed installed_workflow_id/ installed_registry_root (int/list) via `workflow status`, plus regressions locking in the unaffected FileNotFoundError and no-run-id list-path behaviors. Assisted-by: GitHub Copilot (model: Claude Sonnet 5, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: bound workflow step downloads Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: preserve workflow reinstall state Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: fail closed on workflow registry state Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: make workflow installs transactional Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: close workflow transaction races Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: clean up failed workflow transactions Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: clean up workflow removal state Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: guard workflow update transactions Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: harden workflow lifecycle edge cases Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: verify installed workflow ownership Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: isolate workflow rollback cleanup Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: preserve unique workflow backups Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: bind workflow staging to file descriptors Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: fail closed on corrupt workflow registry Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: bind workflow ownership and source identity Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(workflows): harden redirects and Windows tests Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(workflows): restore staged removals on serialization errors Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(workflows): preserve state across interrupted writes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(workflows): harden resume ownership checks Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(workflows): validate persisted run state Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(workflows): validate origin and release metadata Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> |
||
|
|
d956ab722b |
[extension] Update DocGuard — CDD Enforcement extension to v0.32.0 (#3489)
* Update DocGuard — CDD Enforcement extension to v0.32.0 Update docguard extension submitted by @raccioly: - extensions/catalog.community.json (version, download_url, description, updated_at) - docs/community/extensions.md community extensions table Closes #3483 Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: add npx and specify to docguard requires.tools in catalog Assisted-by: GitHub Copilot (model: claude-sonnet-4.5, autonomous) * fix: shorten docguard description to ≤200 chars and correct validator count to 24 - Description was 275 chars, now 196 (under the 200-char catalog limit) - Change "27 validators" → "24 validators" to match v0.32.0 README - Applied to both extensions/catalog.community.json and docs/community/extensions.md Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous) --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> |
||
|
|
e48f134c3b |
[extension] Add Multi-Repo Branch Sync extension to community catalog (#3411)
* Add Multi-Repo Branch Sync extension to community catalog Add multi-repo-sync extension submitted by @sebastienthibaud to: - extensions/catalog.community.json (alphabetical order) - docs/community/extensions.md community extensions table Closes #3406 Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: restore multi-repo-sync sha256 * fix: update multi-repo-sync link text and catalog updated_at - Change extensions.md link text from 'spec-kit-multi-repo-sync' to 'multi-repo-sync' to match extension ID convention - Refresh catalog.community.json top-level updated_at to 2026-07-13T00:00:00Z Assisted-by: GitHub Copilot (model: claude-sonnet-4.5, autonomous) * fix: update multi-repo-sync entry timestamps to 2026-07-13 Set created_at and updated_at to 2026-07-13T00:00:00Z to match the catalog publication date, per add-community-extension/SKILL.md:86-87. Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous) --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> |
||
|
|
a8d3038ece |
[extension] Add Spec Kit Memory extension to community catalog (#3455)
* Add Spec Kit Memory extension to community catalog Add memory extension submitted by @zaytsevand to: - extensions/catalog.community.json (alphabetical order) - docs/community/extensions.md community extensions table Closes #3446 Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: resolve merge conflicts with main branch - extensions/catalog.community.json: keep updated_at 2026-07-10 (more recent) - docs/community/extensions.md: include both Spec Kit Figma (main) and Spec Kit Memory (this PR) in alphabetical order Assisted-by: GitHub Copilot (model: claude-sonnet-4.5, autonomous) * fix: add memsearch optional tool dependency to memory extension catalog entry Assisted-by: GitHub Copilot (model: claude-sonnet-4.5, autonomous) --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> |
||
|
|
5f59a5b238 |
Add Test-First Governance preset to community catalog (#3504)
Add test-first-governance preset submitted by @mnriem to: - presets/catalog.community.json (alphabetical order) - docs/community/presets.md community presets table Closes #3502 Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> |
||
|
|
3c9aa1f81b |
Add Autonomous Run Governance preset to community catalog (#3501)
Add autonomous-run-governance preset submitted by @hindermath to: - presets/catalog.community.json (alphabetical order) - docs/community/presets.md community presets table Closes #3499 Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> |
||
|
|
801ff888ff |
[extension] Add Quality Gates (Enforcement Layer) extension to community catalog (#3431)
* Add Quality Gates (Enforcement Layer) extension to community catalog Add gates extension submitted by @schwichtgit to: - extensions/catalog.community.json (alphabetical order, between fx-to-dotnet and github-issues) - docs/community/extensions.md community extensions table Closes #3414 Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: revert unrelated catalog reformatting and remove empty changelog field from gates entry - Restore original ordering/formatting of aide, checkpoint, critique, threatmodel entries and inline requires.tools objects that were inadvertently reordered in the previous commit - Remove `"changelog": ""` from the gates entry (empty URL is inconsistent with catalog conventions; field should be omitted when no changelog URL exists) Addresses review comments: - github/spec-kit#3431 (comment) — unrelated reformatting/reordering - github/spec-kit#3431 (comment) — empty changelog field Assisted-by: GitHub Copilot (model: claude-sonnet-4.5, autonomous) * Fix gates entry tool requirements: git required, add node and shellcheck optional - Mark git as required (per v0.1.0 README: \"jq and git — the hooks and verify.sh require them\" and release notes: \"Requires Spec Kit >=0.12.0, jq, and git\") - Add node as optional tool (per issue #3414 submission) - Add shellcheck as optional tool (per issue #3414 submission) - Update gates entry updated_at and top-level updated_at to 2026-07-13 Assisted-by: GitHub Copilot (model: claude-sonnet-4.5, autonomous)" --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> |
||
|
|
82c078bb3a |
docs: clarify that release tags keep the leading v prefix (#3463)
Readers were replacing vX.Y.Z with bare versions like 0.12.11, which fails because git tags are named v0.12.11. Assisted-by: Cursor Grok 4.5 (supervised) Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
a3bcd67925 |
docs(bundles): document --integration on 'bundle update' (#3271)
The 'bundle update' command accepts --integration (verified via 'specify bundle update --help' and the command signature), used as the integration override when the project's active integration can't be detected. The Update Bundles options table in reference/bundles.md omitted it, listing only --all and --offline — unlike the install/init tables which already document --integration. Add the missing row. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
c2af5c5a52 |
Add Verify Review Ship extension to community catalog (#3450)
Add verify-review-ship extension submitted by @cadugevaerd to: - extensions/catalog.community.json (alphabetical order) - docs/community/extensions.md community extensions table Closes #3429 Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> |
||
|
|
a10fd2f355 |
docs: document copilot skills mode (--skills) and markdown deprecation (#3313)
* docs: document copilot skills mode (--skills) and markdown deprecation as of #3256 (v0.12.3) the copilot integration supports a skills mode via `--integration-options "--skills"`, and installing without it warns that the legacy markdown default is being phased out. this was undocumented: - the copilot row in the supported-agents table had an empty notes cell while other skills-capable agents describe their behavior there. - `--skills` was missing from the integration-specific options table (only generic and kimi were listed). fill both. wording matches the code: skills scaffold as speckit-<name>/SKILL.md under .github/skills/ and are invoked as /speckit-<name>; without the flag the install emits the deprecation warning from _warn_legacy_markdown_default(). fixes #3300 * docs: describe copilot default as legacy markdown mode (.agent.md + .prompt.md) the copilot rows said the default installs .agent.md files, but the default scaffold also writes companion .prompt.md files under .github/prompts/. also reworded to 'legacy markdown mode' to match the deprecation warning users actually see and to avoid ambiguity, since skills are markdown too. * docs: spell out copilot legacy markdown paths and use <command> in copilot rows address the follow-up copilot review: name where the default scaffold writes files (.github/agents/*.agent.md plus .github/prompts/*.prompt.md and a .vscode/settings.json merge), and switch speckit-<name> to speckit-<command> to match the rest of the table. verified all three paths against the copilot integration source. * docs: use --integration-options="..." form in copilot notes cell match the equals form the rest of the doc uses (generic row, the options table, and the install example) so readers don't mistake the quotes for part of the value. addresses copilot review feedback. |
||
|
|
c8ce488073 |
chore: add pre-commit config and fix trailing whitespace/end-of-file (#3430)
issues |
||
|
|
983a87f3e3 |
Add EARS Requirements Syntax extension to community catalog (#3407)
Add ears extension submitted by @v-dhruv to: - extensions/catalog.community.json (alphabetical order) - docs/community/extensions.md community extensions table Closes #3395 Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> |
||
|
|
e3989e3572 |
Add Spec Kit Figma extension to community catalog (#3408)
Add figma extension submitted by @sebastienthibaud to: - extensions/catalog.community.json (alphabetical order) - docs/community/extensions.md community extensions table Closes #3396 Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> |
||
|
|
3f7392ae32 |
docs: add 'spectatui' entry to friends.md (#3362)
* docs: add 'spectatui' entry to friends.md Added a new entry for 'spectatui', a terminal UI dashboard for GitHub Spec-Kit, detailing its features and capabilities. https://github.com/tinesoft/spectatui * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * docs: fix wording of the `spectatui` tool's decription Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> |
||
|
|
c5fdae752b |
Update Golden Demo extension to v0.3.0 (#3394)
Update golden-demo extension submitted by @jasstt: - extensions/catalog.community.json (version, download_url, description, provides, tags, updated_at) - docs/community/extensions.md community extensions table Closes #3360 Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> |
||
|
|
0da969df14 |
[extension] Add LLM Wiki extension to community catalog (#3361)
* Add LLM Wiki extension to community catalog Add wiki extension submitted by @formin to: - extensions/catalog.community.json (alphabetical order) - docs/community/extensions.md community extensions table Closes #3319 Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: limit catalog.community.json changes to wiki entry + timestamps only Reverts the unintended reordering and reformatting of existing extensions (aide, checkpoint, critique, threatmodel, etc.) and companion's tools array. Only the new wiki entry and updated_at timestamps are now changed. Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous) --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> |
||
|
|
13d2cca154 |
Docs: Document missing CLI flags and integrations (#3182)
* docs: document missing flags and integrations * docs: remove invalid --refresh-shared-infra from upgrade command * docs: address PR feedback for extension and integration flags * docs: reorder extension add options to match CLI help |
||
|
|
f764270d06 |
Add Orchestration Task Context Management extension to community catalog (#3372)
Add orchestration-task-context-management extension submitted by @benizzio to: - extensions/catalog.community.json (alphabetical order) - docs/community/extensions.md community extensions table Closes #3356 Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> |
||
|
|
7839acce86 |
Update DocGuard — CDD Enforcement extension to v0.30.0 (#3371)
Update docguard extension submitted by @raccioly: - extensions/catalog.community.json (version, download_url, description, updated_at) - docs/community/extensions.md community extensions table Closes #3355 Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> |
||
|
|
1935cf7e48 |
Update Ripple extension to v1.1.0 (#3370)
Update ripple extension submitted by @chordpli: - extensions/catalog.community.json (version, download_url, description, requires.tools) - docs/community/extensions.md community extensions table Closes #3354 Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> |
||
|
|
4bb5166445 |
Add Charter extension to community catalog (#3363)
Add charter extension submitted by @Huljo to: - extensions/catalog.community.json (alphabetical order) - docs/community/extensions.md community extensions table Closes #3322 Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> |
||
|
|
288bd679f3 |
docs: drop stale kimi KIMI.md->AGENTS.md migration note (#3291)
* docs: drop stale kimi KIMI.md->AGENTS.md migration note #3097 made the agent-context extension a full opt-in and removed the KIMI.md -> AGENTS.md context migration from the kimi integration (_migrate_legacy_kimi_context_file and the context_file handling are gone). kimi's --migrate-legacy now only moves the skills directory. two lines in the integrations reference still promised the removed context migration; drop that clause so the docs match the code. * docs: clarify kimi legacy migration is skill naming, not directory names address review: the parenthetical said 'dotted->hyphenated directory names', but the migration is about skill naming (speckit.xxx -> speckit-xxx), matching the module docstring. reword to match. |
||
|
|
6288dea6ae |
[extension] Add Analytics extension to community catalog (#3296)
* Add Analytics extension to community catalog Add analytics extension submitted by @Huljo to: - extensions/catalog.community.json (alphabetical order) - docs/community/extensions.md community extensions table Closes #3288 Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix empty changelog field for analytics extension Set the analytics extension changelog to the GitHub releases page instead of an empty string, which the catalog treats as a URI when present and can fail schema validation and downstream tooling. Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Manfred Riem <15701806+mnriem@users.noreply.github.com> |
||
|
|
490566847c |
feat(cli): honor SPECIFY_INIT_DIR in the specify CLI project resolver (#3186)
* feat(cli): honor SPECIFY_INIT_DIR in the specify CLI project resolver The shell resolver honors SPECIFY_INIT_DIR (#2892), but the Python CLI did not: it resolved the project as Path.cwd() + a .specify/ check and never read the override. So setup-plan.sh respected it while `specify integration install` ignored it, and you still had to cd into the member project. Route project resolution through a shared _resolve_init_dir_override() that applies the shell resolver's validation rules (relative to cwd, must exist and contain .specify/, hard error, no fallback, same error strings). It's wired into _require_specify_project() — the chokepoint for every project-scoped subcommand (integration/extension/workflow/preset/...) — and the `workflow run <file>` standalone path, which re-applies its symlinked-.specify guard on the override branch too. init is unchanged: it creates .specify/, so the must-pre-exist rule doesn't apply. The resolver canonicalizes symlinks via Path.resolve() while the shell keeps the logical path; they agree for non-symlinked paths (documented in the resolver). Tests in tests/test_init_dir_cli.py mirror the strict cases from test_init_dir.py through the CLI; conftest now strips SPECIFY_* for the whole suite so a stray export can't perturb the now-env-reading resolver. Docs note the CLI applies the same rules. Discussion: github/spec-kit#2834 (Disclosure: I used an AI coding agent to audit the call sites and resolver, draft the change, and run an adversarial code review; reviewed by me.) * fix(cli): honor SPECIFY_INIT_DIR for bundle commands Assisted-by: Codex (model: GPT-5, autonomous) * fix(bundler): refuse symlinked .specify on the SPECIFY_INIT_DIR override path find_project_root refuses a symlinked .specify (following it could read/write outside the tree, and a test pins that), but the SPECIFY_INIT_DIR override added for bundle commands returned early and skipped that guard: _resolve_init_dir_override validates .specify with is_dir(), which follows symlinks. So `specify bundle` accepted via the override a layout the cwd path rejects. Re-check the override result with the same guard, plus a regression test. (Disclosure: found via an AI code review and fixed with an AI coding agent; reviewed by me.) * fix(cli): keep SPECIFY_INIT_DIR strict for bundles Treat an explicit symlinked SPECIFY_INIT_DIR project as a hard bundle error instead of returning no project, which could initialize the current directory. Align the docs with the actual unset resolver behavior. Assisted-by: Codex (model: GPT-5, autonomous) * docs(core): note symlinked .specify handling differs across CLI surfaces A symlinked .specify is followed by integration/extension/workflow (matching the shell resolver) but refused by bundle and workflow run <file> (write confinement). Document the asymmetry so it reads as intentional. (Disclosure: AI-assisted; reviewed by me.) * docs(core): reframe symlinked .specify note around the override invariant Per maintainer feedback on #3186: SPECIFY_INIT_DIR relocates where the project is, not how a surface treats symlinks. Each surface keeps its cwd-path stance (write surfaces refuse a symlinked .specify, read/config surfaces follow it), so the split is one policy relocated, not an inconsistency. * docs: address Copilot review on resolver docstrings - _project.py: the error messages "mirror" the shell wording rather than "match" it (the CLI renders a Rich `Error:` line, the shell a plain `ERROR:`). - find_project_root: document that honoring SPECIFY_INIT_DIR when start is None can raise typer.Exit / BundlerError, so the Path | None signature isn't surprising to direct callers. * docs(bundler): note require_project_root inherits the override raise behavior find_project_root can raise typer.Exit / BundlerError under the SPECIFY_INIT_DIR override (start=None); require_project_root inherits that, so document it alongside its own BundlerError-on-missing-project. * docs: clarify symlinked project root behavior Assisted-by: OpenAI Codex (model: GPT-5, autonomous) * Address SPECIFY_INIT_DIR review feedback Assisted-by: OpenAI Codex (model: GPT-5, autonomous) * Route workflow JSON errors to stderr Assisted-by: OpenAI Codex (model: GPT-5, autonomous) |
||
|
|
876e532d76 |
docs(toc): add Bundles and Authentication to the Reference nav (#3267)
docs/reference/bundles.md and docs/reference/authentication.md exist on disk but were absent from the Reference section of docs/toc.yml, so both pages were orphaned and undiscoverable in the published docs sidebar. Add the two nav entries (Bundles after Workflows, matching the ordering in reference/overview.md; Authentication last). Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
36501d459f |
chore: retire Roo Code integration — extension shut down (#3167) (#3212)
* chore: retire roo integration — extension shut down (#3167) Remove the Roo Code integration after the extension was shut down: subpackage, registry entry, catalog entry, docs, tests, and issue-template options. Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: remove stale Roo Code mention in upgrade guide Assisted-by: GitHub Copilot (model: gpt-5.3-codex, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore: remove leftover Roo Code references after merge Drop roo from presets/ARCHITECTURE.md example and the agent-context defaults map; these came in from main and were flagged by review. Assisted-by: GitHub Copilot (model: claude-opus-4.8, autonomous) --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> |
||
|
|
c47dd2b812 |
chore: retire Windsurf integration — absorbed into Cognition Devin (#3168) (#3213)
* chore: retire windsurf integration — absorbed into Cognition Devin (#3168) windsurf.com now permanently redirects to devin.ai/desktop following acquisition. Remove subpackage, registry/catalog entries, docs, and tests; re-point sample-agent test fixtures to Kilo Code. Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: remove stale Windsurf support references Assisted-by: GitHub Copilot (model: gpt-5.3-codex, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: fix Kilo Code command path in upgrade guide Assisted-by: GitHub Copilot (model: gpt-5.3-codex, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * chore: align integration lists after rebase Assisted-by: GitHub Copilot (model: gpt-5.3-codex, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * docs: align kilocode example with runtime behavior Assisted-by: GitHub Copilot (model: gpt-5.3-codex, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> |
||
|
|
844c73685b |
[extension] Update Intake extension to v0.1.3 (#3254)
* Update Intake extension to v0.1.3 Update intake extension submitted by @bigsmartben to: - extensions/catalog.community.json (version, download_url, description, provides.commands, updated_at) - docs/community/extensions.md community extensions table Closes #3247 Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Revert catalog-wide formatting churn; keep intake-only changes Addresses review feedback on PR #3254: the previous commit re-serialized the entire community catalog (escaping Unicode punctuation like — to \u2014 and reformatting unrelated entries). Restore the catalog to its prior formatting and limit the diff to the intake entry (version, download_url, description, provides.commands, updated_at). Assisted-by: GitHub Copilot (model: claude-opus-4.8, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Manfred Riem <15701806+mnriem@users.noreply.github.com> |
||
|
|
9c691e57b9 |
Update Architecture Workflow extension to v1.2.2 (#3255)
Update arch extension submitted by @bigsmartben to: - extensions/catalog.community.json (version, download_url, description, commands count) - docs/community/extensions.md community extensions table Closes #3246 Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> |
||
|
|
ada293e203 |
Add Repository Governance extension to community catalog (#3252)
Add repository-governance extension submitted by @bigsmartben to: - extensions/catalog.community.json (alphabetical order) - docs/community/extensions.md community extensions table Closes #3245 Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> |
||
|
|
28a38af6c1 |
chore: retire iflow integration — product discontinued (#3166) (#3211)
Remove the iFlow CLI integration whose product was shut down: subpackage, registry entry, catalog entry, docs, tests, and issue-template options. Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> |
||
|
|
8215f3308b |
docs(codebuddy): fix dead install links and CodeBuddy capitalization (#3172) (#3216)
* fix(codebuddy): repoint install_url to codebuddy.cn (#3172) The codebuddy.ai domain no longer resolves; CodeBuddy consolidated onto codebuddy.cn (Tencent). Update install_url and docs links to https://www.codebuddy.cn/cli (verified live). Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: use canonical 'CodeBuddy' capitalization in installation prereqs Address Copilot review: the link text read 'Codebuddy CLI' while the rest of the docs and the integration metadata use 'CodeBuddy'. Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> |