The Claude Code integration installs skills into `.claude/skills` (see
integrations/claude: `"dir": ".claude/skills"`), and the "what gets kept"
list earlier in this same doc already says `.claude/skills/`. But three
troubleshooting/reference spots still point users at `.claude/commands/`,
which does not exist for a Claude Code install -- so the "verify files
exist" checks list an empty/missing directory. Correct all three to
`.claude/skills/`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: clarify shell-step interpolation safety
Shell step `run` fields are executed by the system shell and `{{ ... }}`
expressions are substituted as raw, unquoted text. Document that untrusted
sources — workflow `inputs.*` and prior-step output, including AI-generated
`prompt` output — must be quoted, enum-constrained, validated, or gated before
they reach a `run` field.
- docs/reference/workflows.md: add an "Interpolation and shell safety" section.
- workflows/README.md: add a warning under the Shell Steps example, link to the
new section, and quote the `inputs.project_dir` example.
- workflows/PUBLISHING.md: strengthen the interpolation guidance and call out
prior-step/agent output as untrusted.
Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c0fd6900-69bf-4fcb-b377-de37f98f5835
* docs: correct shell-step interpolation guidance
Address review feedback that the previous wording over-promised. Clarify that
none of the mitigations neutralise a hostile interpolated value:
- Quoting is not a security boundary — there is no shell-escaping filter, and a
value containing the matching quote can break out. Present quoting as
correctness handling for already-constrained values only.
- Remove the "pass data via environment or files" guidance: ShellStep has no
`env` mapping (it only copies the process environment and sets
SPECKIT_WORKFLOW_DIR), so that transport does not exist.
- Drop the claim that routing through a command/prompt step validates or safely
binds a value; it does not.
- Correct the gate guidance: a gate renders only its own message/show_file and
does not inspect, resolve, or sanitise the following step. Authors must
surface the exact command/data in the gate themselves, and approval does not
neutralise an injectable interpolation.
Frame constraining values at the source (enum/allowlist) as the only reliable
control, and keeping unconstrained values out of `run` fields entirely.
Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c0fd6900-69bf-4fcb-b377-de37f98f5835
* docs: remove unsafe interpolation from example and gate guidance
Address further review feedback:
- workflows/README.md: the shell example interpolated an unconstrained path
into shell source, which contradicted the warning beneath it. Shell steps
already run from the project root, so drop the `cd '{{ inputs.project_dir }}'`
prefix and model a plain `run: "npm test"` with no interpolation.
- docs/reference/workflows.md: GateStep prints `message` verbatim with no
control-character stripping (stripping applies only to `show_file` path and
contents), so recommending that authors surface untrusted data in `message`
was itself unsafe — agent/caller output could inject terminal escapes to
alter or hide the prompt. Direct authors to keep `message` to trusted text
and surface untrusted material via `show_file`, whose contents are stripped.
Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c0fd6900-69bf-4fcb-b377-de37f98f5835
* docs: use correct prompt-step output key in example
A `prompt` step stores agent-generated text under `output.stdout`, not
`output.value`, so the example expression `{{ steps.plan.output.value }}`
would resolve to None. Reference `output.stdout` so the example correctly
demonstrates untrusted agent output flowing into a shell step.
Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c0fd6900-69bf-4fcb-b377-de37f98f5835
---------
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c0fd6900-69bf-4fcb-b377-de37f98f5835
* fix(integrations): declare PiIntegration multi_install_safe
PiIntegration writes only to its isolated, static root .pi/prompts,
disjoint from every other integration, yet never declared
multi_install_safe — so it inherited the IntegrationBase default False,
leaving `specify integration status` in a permanent unsafe-multi-install
ERROR state when pi is co-installed alongside another agent.
Add `multi_install_safe = True`, mirroring the isolated MarkdownIntegration
cohort (qwen, shai, qodercli) and the kiro-cli #3471 fix. The parametrized
registry isolation contracts auto-include pi and pass.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(integrations): list pi in the multi-install-safe reference table
Declaring PiIntegration multi_install_safe means the reference table in
docs/reference/integrations.md (which states it lists all currently
declared multi-install-safe integrations) should include it. Add the
alphabetized pi row with its .pi/prompts isolation path.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Update Cross-Platform Governance preset to v0.2.1
Update cross-platform-governance preset submitted by @hindermath to:
- presets/catalog.community.json (version, download_url, documentation, description, updated_at)
- docs/community/presets.md community presets table
Closes#3683
Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* ci: pin ruff to 0.15.0 to avoid 0.16.0 default-ruleset breakage
ruff 0.16.0 expanded its default rule set from ~59 to ~413 rules,
causing the unpinned `uvx ruff check` step to report 1476 pre-existing
violations. Pin to 0.15.0 to restore green lint until the codebase is
audited against the new defaults.
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: Manfred Riem <15701806+mnriem@users.noreply.github.com>
* fix(integrations): declare OmpIntegration multi_install_safe
OmpIntegration is a plain MarkdownIntegration whose files live only under
its isolated, static root .omp/commands/, disjoint from every other
integration. But it never declared multi_install_safe, so it inherited the
IntegrationBase default False — leaving `specify integration status` in a
permanent unsafe-multi-install ERROR state whenever omp is co-installed
alongside another agent, with no acknowledgment path.
Add `multi_install_safe = True`, mirroring the isolated MarkdownIntegration
cohort (qwen, shai, qodercli, junie, kilocode) and the kiro-cli #3471 fix.
The parametrized registry isolation contracts auto-include omp once the flag
is set and pass (.omp/commands is isolated and its manifest disjoint).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(integrations): list omp in the multi-install-safe reference table
Declaring OmpIntegration multi_install_safe means the reference table in
docs/reference/integrations.md (which states it lists all currently
declared multi-install-safe integrations) should include it. Add the
alphabetized omp row with its .omp/commands isolation path.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: add spec-kit-copilot to community friends
Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c4ad8a5e-3ddb-44b2-b8ec-4f438c636b61
* docs: note first-party exception for spec-kit-copilot
Clarify the page disclaimer so it covers first-party GitHub projects, and
mark spec-kit-copilot as a first-party GitHub project.
Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c4ad8a5e-3ddb-44b2-b8ec-4f438c636b61
---------
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c4ad8a5e-3ddb-44b2-b8ec-4f438c636b61
* tidy kiro-cli multi-install-safe declaration + add docs row and test
kiro-cli is declared multi_install_safe (it uses a fully isolated .kiro/
root, .kiro/prompts command dir, a stable '.' separator, and a dedicated
manifest — #3471). main ended up with the flag assigned twice in the class
body; this drops the bare duplicate and keeps a single declaration with the
comment explaining why it's safe.
also adds the missing kiro-cli row to the multi-install-safe table in
docs/reference/integrations.md, and a test asserting the flag is set (the
registry contract tests already enforce the actual path isolation against
every other safe integration).
* address review: drop agent-file column and duplicate test
per maintainer guidance on #3477: the Isolation table listed each
integration's agent-context file (AGENTS.md, CLAUDE.md, etc.) alongside
its command dir, which contradicted the safety definition (Copilot flagged
kiro-cli/codex both mapping to AGENTS.md). those context files are owned by
the optional agent-context extension, not by multi_install_safe — the flag
governs only the command directory + manifest.
- renamed the column to 'Command directory' and removed the agent-file
entries, so it lists only what each integration actually manages
- reworded the definition to note agent-context is a separate concern and
is not multi-install safe
- removed the duplicate test_declared_multi_install_safe (the existing
test_declares_multi_install_safe already asserts the same thing)
* address review: keep agent-root requirement; reframe agent-context targeting
- Restore 'static, unique agent root' alongside command directory in the
multi-install-safe definition — base.py and test_registry.py
(test_safe_integrations_have_distinct_agent_roots) enforce both.
- Reframe the agent-context note: multi_install_safe is an integration-level
declaration about command/skill paths, so describe context-file targeting
as independent of it rather than calling the extension 'not multi-install
safe'. The extension can even sync multiple anchors via context_files.
* address review: hoist multi_install_safe to top of class
Move the multi_install_safe = True declaration to the top of
KiroCliIntegration (right after key) so it is visible at the exact spot
the diff touched. The flag was never actually removed — it was declared
once further down the class — but placing it at the top makes the opt-in
unmistakable in review and keeps the single declaration. Verified the
integration still resolves multi_install_safe is True; 24 kiro-cli tests
pass.
* docs(scripts): document the 'py' script type and sh/ps migration plan (#3284)
Bring remaining docs up to date with the Python (`py`) workflow-script
variant introduced in #3277, and record the retention/deprecation plan
for the shell variants.
- AGENTS.md: document the `scripts:` frontmatter (sh/ps/py), clarify the
`{SCRIPT}` placeholder resolution, and add a "Script Types and
Migration" section (why py is recommended, defaults, phased sh/ps
deprecation path). Note the Python agent-context variant.
- docs/quickstart.md, docs/local-development.md: mention the `py` variant
and `--script sh|ps|py`.
- docs/reference/integrations.md: add `py` to the `--script` rows for
install/switch/upgrade.
- .devcontainer/devcontainer.json: auto-approve `.specify/scripts/python/`.
Closes#3284.
Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 298d6ec2-a330-49bc-9394-fe2b77f25ff3
* docs(scripts): address review — accurate paths, prompt behavior, scoped py claims
Addresses the review on PR #3653:
- AGENTS.md: fix the agent-context Python path to its real
`extensions/agent-context/scripts/python/` location.
- AGENTS.md: qualify that only templates that invoke a helper script
carry `scripts:` frontmatter (constitution/specify do not).
- AGENTS.md: narrow the availability claim — `py` covers the core
command templates; the bundled extensions ship Python scripts on disk
but their command templates still invoke shell variants, so `--script
py` does not yet route extension commands to Python.
- AGENTS.md / quickstart / local-development: describe the interactive
prompt vs. non-interactive OS default instead of "auto-selects".
- local-development: add `--script py` to the wrong-script-type fix.
Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 298d6ec2-a330-49bc-9394-fe2b77f25ff3
* docs(scripts): trim deprecation timeline, correct parity/scope claims (review)
Addresses the second review on PR #3653:
- Remove the speculative four-phase deprecation timeline from AGENTS.md.
A forward-looking removal schedule is roadmap content, not contributor
guidance, and its phases lacked an actionable adoption signal. Replace
it with the concrete contributor parity rule plus a one-line
current-posture note pointing removal work to the #3277 epic.
- Stop stating dual-maintenance as already eliminated: reframe "single
source of truth" as the intended direction, noting all three variants
are still maintained in parallel today.
- Correct the parity-coverage claim: Python ports have output-parity
tests where the contract is stdout-based and unit tests elsewhere,
rather than every file being compared to every shell counterpart.
- Scope the `scripts:` frontmatter rule to core command templates and
note the agent-context/git extension templates don't use it yet.
Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 298d6ec2-a330-49bc-9394-fe2b77f25ff3
---------
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
* docs(concepts): document the spec-of-specs feature breakdown approach (#3423)
Add a dedicated "Spec of Specs" concept page describing how to decompose a
large feature into a roadmap of smaller, independently-specified sub-features
using the existing Spec Kit flow. Covers the roadmap pass, the roadmap
artifact template, specifying each sub-feature, bidirectional sub-spec/roadmap
linking, keeping them in sync, a worked example, and optional automation.
Link the new page from the "Handling Complex Features" decomposition section
and add it to the docs table of contents.
Closes#3423
Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f236bc38-a7c6-4063-a79c-6ba81aa685b5
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Adds a skills-based integration for the Factory Droid CLI alongside the
existing Claude/Codex skills agents. The integration scaffolds
`.factory/skills/speckit-*` directories and documents the install step
in the devcontainer post-create script via the official npm
distribution (`npm install -g droid`), which matches the layout of every
other CLI install block above and avoids executing an unverified remote
shell installer. The integration is also added to the user-facing
supported-agent table in `docs/reference/integrations.md` so the new key
is discoverable from the published documentation, as required by the
"Updating this documentation" guideline in AGENTS.md.
Operator-supplied extra args via `SPECKIT_INTEGRATION_DROID_EXTRA_ARGS`
are appended after the canonical Spec Kit flags so the canonical flags
are always present in argv. The Factory Droid CLI parser uses
last-wins duplicate-flag semantics (verified empirically against
droid 0.175.0), so a later operator-supplied value may override the
canonical one — this is a deliberate inversion of the cursor-agent /
opencode / codex ordering.
Includes:
- `src/specify_cli/integrations/droid/__init__.py` (subpackage)
- `tests/integrations/test_integration_droid.py` (46 tests, including
regression coverage for the no-trailing-newline frontmatter fusion
bug, idempotent skill injection, and env-var path resolution)
- `integrations/catalog.json` entry + `updated_at` bump
- Alphabetical registration in `src/specify_cli/integrations/__init__.py`
and `tests/integrations/test_registry.py`
- Devcontainer Droid install block via the npm distribution
(`npm install -g droid`), replacing the earlier curl-based installer
- User-facing supported-agent table row in
`docs/reference/integrations.md` (key `droid`, `.factory/skills/`
layout, `/speckit-<command>` invocation)
- `AGENT_CONFIG` entry and matching alphabetical entries in
`tests/test_agent_config_consistency.py` (`ISSUE_TEMPLATE_AGENT_KEYS`)
and the three issue-template dropdowns (`agent_request.yml`,
`bug_report.yml`, `feature_request.yml`) so
`test_issue_template_agent_lists_match_runtime_integrations` keeps
the runtime/template surfaces synchronized
Closes#822
Assisted-by: Droid (oracle-reviewer)
Assisted-by: Droid (model: MiniMax M3, autonomous)
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
* docs(installation): document the 'py' (Python) script type
The installation guide's "Specify Script Type" section only showed
`--script sh` and `--script ps`, and the installed-variant list only
mentioned `.specify/scripts/bash/` and `.specify/scripts/powershell/`.
The CLI has a third script type, `py`: `SCRIPT_TYPE_CHOICES` in
`_agent_config.py` includes `"py": "Python"`, and `shared_infra.py`
installs a `python/` variant directory (plus the platform shell fallback)
when `--script py` is chosen.
Add the missing `--script py` force example and a `.specify/scripts/python/`
bullet so the docs match the code. Docs-only.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(installation): frame Python as a first-class third script type
Address review feedback: update the section heading and intro so Python
is presented as a first-class script type alongside Shell and PowerShell,
matching the added --script py example and python/ variant directory.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
'specify init --script' accepts sh, ps, or py (init.py + SCRIPT_TYPE_CHOICES in
_agent_config.py), and 'specify init --script py' scaffolds Python scripts. The
core.md option reference listed only 'sh|ps', so a user following the canonical
docs never learns about --script py. Update the option row to sh|ps|py.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: update Bob integration to skills-based layout for Bob 2.0
Bob 2.0 replaces the command-based workflow (.bob/commands/*.md) with
a skills-based layout (.bob/skills/speckit-<name>/SKILL.md), matching
the pattern used by Claude Code, Codex, and other skills-first agents.
- Switch BobIntegration from MarkdownIntegration to SkillsIntegration
- Update folder/dir from .bob/commands to .bob/skills
- Change extension from .md to /SKILL.md (skills layout)
- Add --skills option (default: True) consistent with Codex pattern
- Update tests to inherit from SkillsIntegrationTests (28 tests pass)
- Bump catalog entry to version 2.0.0 with updated description
Assisted-by: IBM Bob (model: claude-sonnet-4-5, autonomous)
* PR comments fix: keep old Bob 1 commands till next release
* Copilot suggested change
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* feat(bob): address copilot comments, make skills layout default, demote legacy commands to opt-in
* fix(bob): honor legacy_commands in ai_skills persistence and add bob to ALWAYS_SLASH_AGENTS
- init.py: suppress ai_skills=True when --legacy-commands is passed so
extensions and presets target .bob/commands, not .bob/skills
- _invocation_style.py: add 'bob' to ALWAYS_SLASH_AGENTS so init next-steps
and hook invocations always show /speckit-<name> (skills is the default
layout; no ai_skills flag required)
* Copilot suggestion
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* fix(bob): extend IntegrationBase directly to avoid false isinstance(SkillsIntegration)
- bob/__init__.py: switch BobIntegration base from SkillsIntegration to
IntegrationBase; add _BobSkillsHelper for skills-mode delegation; set
invoke_separator='-' explicitly; set _skills_mode flag in setup() so
consumers can derive the effective mode without isinstance checks
- _helpers.py: replace isinstance(integration, SkillsIntegration) guard
with getattr(_skills_mode) so legacy-commands mode does not persist
ai_skills=True
- _invocation_style.py: remove 'bob' from ALWAYS_SLASH_AGENTS — Bob 2.0
skills are invoked via natural language, not /skill-name slash commands
- integrations/catalog.json: advance updated_at to 2026-07-15
* fix(lint): remove unused SkillsIntegration import from _helpers.py
* Copilot suggested change
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* feat(bob): add bob skills integration with registrar-based mode detection
* address 3 comments from copilot
* feat(bob): update registrar config to use legacy commands layout
* fix lint
* Suggested fix from Copilot
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* fix pr comment
* fix pr comment
* fix pr comment
* refactor(bob): resolve skills mode via base-class hooks + fix command-ref separators
Rework the dual-mode handling introduced for Bob 2.0 so an integration's
internal representation never leaks into shared init/install/upgrade code,
and fix the legacy command-reference separator surfaced in review.
Base-class contract:
- Add IntegrationBase.is_skills_mode(parsed_options) — the single hook the
shared machinery consults to decide whether to persist ai_skills and render
skill invocations. SkillsIntegration returns True; Copilot honors --skills /
self._skills_mode; Bob returns `not legacy_commands`.
- Add IntegrationBase.invoke_separator_for_mode(skills_enabled) — resolves the
command-ref separator from a project's persisted mode for registration paths
that only have the ai_skills flag (no CLI parsed_options). Default is
behavior-preserving; Bob maps skills->"-", legacy->".".
- BobIntegration stays on IntegrationBase (mirroring Copilot, the other
dual-mode agent) and delegates setup() to internal _BobSkillsHelper /
_BobMarkdownHelper. Removes the _skills_mode method and all
isinstance(SkillsIntegration) / callable(_skills_mode) probing from
_helpers.py and init.py.
Fix legacy separator (review feedback): CommandRegistrar.register_commands and
PresetManager._resolve_skill_command_refs previously read the single static
AGENT_CONFIGS[key]["invoke_separator"], so legacy .bob/commands/ extension and
preset command refs rendered /speckit-<cmd> instead of Bob 1.x /speckit.<cmd>.
Both now resolve the separator per project mode via invoke_separator_for_mode.
Tests: add regression coverage for the is_skills_mode / invoke_separator_for_mode
hooks and legacy extension command-ref separators; normalize a width-sensitive
workflow assertion to match its siblings. Full suite green.
Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 63f93544-a77f-4f01-bf04-c88806a97dbf
* fix(bob,copilot): address review — preserve legacy layout, dual-mode separators, extension-skill token resolution
Addresses PR review 4716036212 (3 comments):
1. Bob legacy-install regression: `use`/`switch`/`upgrade` on an existing
Bob 1.x project (only `.bob/commands/` on disk, no stored
`legacy_commands`) called `is_skills_mode(None)` -> True and rewrote
`ai_skills=True`, silently switching extension/command-reference handling
to the skills layout. `is_skills_mode` now takes an optional `project_root`;
Bob preserves an already-installed legacy layout until an explicit upgrade
creates `.bob/skills/`. A fresh project still defaults to skills.
2. Copilot dual-mode separator: `invoke_separator_for_mode` was inherited
from the base (mode-independent) and returned Copilot's static `.`, so
preset/extension command refs in a Copilot skills project rendered
`/speckit.<name>` instead of `/speckit-<name>`. Override it on Copilot to
track the persisted `ai_skills` state, consistent with
`build_command_invocation` and `effective_invoke_separator`.
3. Bob extension-skill command-ref tokens: verified that merging main's
generic `_resolve_command_ref_tokens` (#3544) resolves Bob's tokens via
the `CONDITIONAL_SLASH_AGENTS` path (`/speckit-<name>`); added Bob to the
command-ref regression parametrize plus dedicated Bob use-path tests.
All tests pass (full suite green; merged with current main incl. #3544).
Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 63f93544-a77f-4f01-bf04-c88806a97dbf
* fix(bob): resolve command-ref separator with project-aware mode before shared-infra refresh (review #3415)
The `use`/`switch` paths refresh shared infrastructure via
`_with_integration_setting()` / `_invoke_separator_for_integration()`,
which previously resolved the invoke separator through
`effective_invoke_separator` / `is_skills_mode` WITHOUT a project_root.
For a pre-PR Bob 1.x project (.bob/commands/ on disk, no stored options),
this defaulted to the skills "-" separator and rewrote rendered
shared-template command refs to /speckit-*, even though ai_skills stayed
false. Thread project_root through effective_invoke_separator, the two
runtime helpers, and every call site so Bob's on-disk legacy detection
governs the separator before shared infra is refreshed.
Add a rendered-shared-template regression test covering `use --force`.
Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 63f93544-a77f-4f01-bf04-c88806a97dbf
* fix(bob): scope persisted ai_skills flag to active agent when resolving command-ref separator (review #3415)
`register_commands` runs once per detected agent, but the persisted
`ai_skills` flag describes only the active integration (`opts["ai"]`).
When another agent (e.g. Copilot) is active in skills mode while a
legacy `.bob/commands` layout is also present, the previous code passed
that global `True` to Bob's `invoke_separator_for_mode`, rewriting Bob
1.x command refs to `/speckit-*` instead of `/speckit.*`.
Only consult the persisted flag for the agent it describes
(`opts["ai"] == agent_name`); otherwise resolve the separator from the
agent's own project-aware `effective_invoke_separator(None, project_root)`.
Add regression tests covering the mismatched-active-agent case and a
control for Bob-active skills mode.
Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 63f93544-a77f-4f01-bf04-c88806a97dbf
* fix(bob): detect Spec Kit layout from managed artifacts, not any skills dir (review #3415)
Two related mis-detections from review 4723246468:
1. `BobIntegration.is_skills_mode` treated the mere presence of a
`.bob/skills/` directory as proof the project is skills-based. A legacy
Spec Kit install (managed `.bob/commands/speckit.*.md`) that also carried
unrelated Bob 2 skills would be misclassified as skills, so
`integration use bob` persisted `ai_skills` and rewrote shared refs.
Now the layout is inferred from managed Spec Kit artifacts: legacy/command
mode only when managed `speckit.*.md` command files exist and no managed
`speckit-*` skill dirs do.
2. The `register_commands` separator for an inactive agent used a disk-based
`effective_invoke_separator(None, project_root)` fallback that could pick
the skills separator even though the registrar writes the static command
layout (`.bob/commands/*.md`). Inactive agents now resolve the separator
from the registrar's actual output layout (`extension == "/SKILL.md"`),
so command-layout files keep `/speckit.*` refs regardless of sibling dirs.
Update the affected hook/E2E tests to use managed artifacts and add
regression tests for the mixed-layout and inactive-registrar scenarios.
Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 63f93544-a77f-4f01-bf04-c88806a97dbf
* fix(bob): apply managed-artifact detection on upgrade + consistent skill post-processing (review #3415)
Two issues from review 4723782860:
1. `BobIntegration.setup()` resolved the layout via `is_skills_mode(parsed_options)`
WITHOUT `project_root`, so `integration upgrade bob` on a Bob 1.x install
(managed `.bob/commands/speckit.*.md`, no stored options) ignored the
existing command files, generated skills, and stale-deleted the legacy
commands — silently migrating the project. Pass `project_root` so the same
managed-artifact detection used by `use` also governs upgrades.
2. Only `_BobSkillsHelper` overrode `post_process_skill_content` to suppress
the shared slash-command hook note. Preset/extension skill generators call
that hook on the registered `BobIntegration`, which inherited
`IntegrationBase`'s note-injecting default. Repeat the no-op (delegating to
the skills helper) on the registered class so every Bob skill-generation
path is consistent with intent-activated core Bob skills.
Add regression tests for the upgrade-preservation and post-processing paths.
Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 63f93544-a77f-4f01-bf04-c88806a97dbf
* feat(bob): add --skills migration opt-in; fix separator + manifest loss (review #3415)
Address review #3415 (4724160183):
- Comment 1: Add an explicit `--skills` opt-in to BobIntegration. It forces
the skills layout over on-disk auto-detection, giving legacy Bob 1.x
installs a supported migration path
(`integration upgrade bob --integration-options="--skills"`). `--skills`
and `--legacy-commands` are mutually exclusive (clean exit-1 error).
- Comment 2: In CommandRegistrar.register_commands, derive the command-ref
separator from the output layout (agent_config["extension"]) for the
active agent too, not the persisted ai_skills flag. A command-layout file
(.bob/commands/*.md, .github/agents/*.agent.md) always renders /speckit.*;
only a /SKILL.md scaffold uses /speckit-*. Dual-layout agents (Bob,
Copilot) write skills via their own setup()/skills path, so
register_commands only ever emits their command-layout files.
- Comment 3: Update docs/reference/integrations.md Bob entry to document the
skills-based default (.bob/skills/), the deprecated --legacy-commands
opt-out, and the --skills migration path.
Also fix a latent manifest-loss bug surfaced by the migration path: the
upgrade Phase 2 stale-file cleanup built a throwaway manifest sharing the
integration key and called uninstall(), which always deleted
{key}.manifest.json. Any layout-shrinking upgrade (e.g. legacy->skills)
thus wiped the freshly-saved manifest, leaving the project untracked and
un-upgradeable. uninstall() now takes remove_manifest (default True); the
stale-cleanup pass passes False.
Adds regression tests for the --skills opt-in, mutual exclusion, corrected
active-agent separator, remove_manifest=False, and an end-to-end
legacy->skills migration that verifies the manifest survives and the
project remains upgradeable. Full suite: 4555 passed, 5 skipped.
Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 63f93544-a77f-4f01-bf04-c88806a97dbf
* docs(agents): align token-resolution comment with output-layout separator rule (review #3415)
Address review #3415 (4725516805). The comment above resolve_command_refs
still described the removed state-based behavior ("resolve it from the
integration using the project's persisted skills state"). Update it to
describe the output-layout rule that register_commands now uses: _sep is
derived from the layout this registrar writes (a /SKILL.md scaffold uses the
skills separator; a command-layout file uses the command separator), not the
persisted ai_skills state. Comment-only change; no behavior change.
Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 63f93544-a77f-4f01-bf04-c88806a97dbf
* fix(bob): reconcile extension artifacts on layout change (review #3415)
When a dual-mode agent (Bob) flips between the legacy commands layout and
the skills layout during `integration upgrade` (via `--skills` /
`--legacy-commands`), the old layout's extension command/skill files were
left orphaned: Phase 2 stale cleanup only removes files tracked by the
*integration* manifest, while extension artifacts are tracked in the
extension registry. Detect the layout flip by comparing whether the old vs
new manifest tracks a `/SKILL.md` scaffold, and when it changed, unregister
the agent's extension artifacts before the existing re-registration so they
are recreated in the new layout (and the per-agent registry is updated).
Preset artifacts are documented as a known, pre-existing cross-cutting gap:
no agent-scoped preset re-registration exists in use/switch/upgrade for any
agent, so reconciling them is out of scope for this Bob migration.
Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 63f93544-a77f-4f01-bf04-c88806a97dbf
* fix(bob): reject layout migration when preset overrides are installed (review #3415)
A command↔skills layout change during `integration upgrade` cannot reconcile
preset artifacts: presets track their command/skill files in per-preset
`registered_commands`/`registered_skills` metadata, and there is no
agent-scoped preset re-registration anywhere in the CLI. Migrating would
delete a preset's old-layout files without recreating them in the new layout
and leave the preset registry claiming artifacts that no longer exist.
Detect the intended layout via `is_skills_mode` (so a plain same-layout
upgrade is unaffected) and, when it flips while preset overrides are
installed for the agent, reject the upgrade *before any mutation* with an
actionable error pointing at the remove → upgrade → reinstall workaround.
Extension artifacts are still reconciled for the safe (no-preset) case.
Adds a regression test and documents the migration caveat in the Bob
integration reference entry.
Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 63f93544-a77f-4f01-bf04-c88806a97dbf
* fix(bob): restrict layout reconciliation to the active integration (review #3415)
`integration_upgrade` supports upgrading a secondary (non-active) integration,
but the layout-change extension reconciliation was unsafe there.
`ExtensionManager.unregister_agent_artifacts()` treats the unscoped
per-extension `registered_skills` list as belonging to the passed agent and,
when that agent's skills directory is absent, falls back to scanning every
agent's skills directory — so reconciling a secondary Bob layout flip could
delete or untrack the *active* agent's extension skills. The subsequent
re-registration cannot repair that because extension skill rendering is
intentionally scoped to the active agent (#2948).
Gate the unregister-before-register reconciliation on `installed_key == key`
so it only runs for the active integration. Secondary agents only ever have
extension command files (skills are active-agent-only), which the existing
re-registration rewrites in place, so skipping the unregister orphans nothing
new. Adds a regression test asserting a secondary Bob layout change leaves the
active agent's extension skill intact on disk and in the registry.
Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 63f93544-a77f-4f01-bf04-c88806a97dbf
* fix(bob): fail closed when preset registry is unreadable (review #3415)
Address review 4744636079:
- _migrate_commands: the preset guard previously failed *open* — a
registry read/parse error returned an empty "no presets" list, so a
--force layout-changing upgrade could delete preset-overridden command
files while their registry state was unknown. Read the registry file
directly and raise _PresetRegistryUnreadableError on any read/parse
failure or malformed structure, rejecting the migration before any
mutation. A genuinely absent registry still returns [] (safe).
- bob: correct the is_skills_mode docstring — upgrade *does* run setup();
disk detection is needed because legacy Bob 1.x installs never persisted
a legacy_commands option, so the stored mode is unavailable.
- tests: add fail-closed E2E (corrupted registry rejected, valid-empty
allowed) plus a unit test for _installed_presets_affecting_agent covering
absent / corrupted / malformed / valid / affecting-agent cases.
Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 63f93544-a77f-4f01-bf04-c88806a97dbf
* fix(bob): fail closed on malformed preset entries too (review #3415)
Address review 4745191015: the preset guard read a parseable registry but
silently skipped malformed per-preset metadata and treated a malformed
registered_commands value as "no matching artifacts". A registry such as
{"presets":{"p1":[]}} therefore allowed a layout migration even though p1's
ownership is unknown, risking deletion of preset-managed files. Now raise
_PresetRegistryUnreadableError for a non-dict preset entry, a non-dict
registered_commands, or a non-list registered_skills. Extend the unit test
to cover these malformed shapes.
Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 63f93544-a77f-4f01-bf04-c88806a97dbf
---------
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Manfred Riem <15701806+mnriem@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
* feat(workflows): add standalone WorkflowResolver and overlay subsystem
Implement PR 1 of the workflow-overlays plan: a concrete, standalone
WorkflowResolver for downstream workflow extensibility without touching the
Preset subsystem.
- Add overlay manifest schema (Overlay, OverlayEdit, validate_overlay_yaml)
- Add pure-function merge engine (find_step, apply_edit, merge_steps,
validate_edits) with recursive anchor search and higher-wins semantics
- Add StepListComposer and tiered layer sources (project, installed, base)
- Add WorkflowResolver facade with inline HIGHER_WINS priority sorting
- Add CLI verbs: workflow overlay add/set-priority/enable/disable/remove/list
and workflow resolve <id>
- Wire WorkflowEngine.load_workflow through WorkflowResolver
- Extend workflow add to copy optional overlays/ subdirectory from local
workflow directories
- Add comprehensive unit, integration, and security tests
Refs: discussion #3473 (https://github.com/github/spec-kit/discussions/3473)
Assisted-by: Kimi (model: opencode-go/kimi-k2.7-code, autonomous)
* fix(workflows): reject symlinked overlay directories in layer sources
Address PR #3557 review comments r3594064534 and r3594064563:
- ProjectOverlaySource.collect now rejects symlinked per-workflow overlay
directories (.specify/workflows/overlays/<id>) before iterating
- InstalledOverlaySource.collect now rejects symlinked installed overlay
directories (.specify/workflows/<id>/overlays) before iterating
- workflow_overlay_list catches ValueError from resolver and exits with
code 1 instead of crashing on unhandled exceptions
- Added .specify/workflows/overlays to _reject_unsafe_workflow_storage
chokepoint for defense-in-depth
These guards prevent symlinked overlay directories from redirecting
auto-loaded overlay YAML to attacker-controlled content outside the
project, which could inject executable shell steps into trusted workflows.
Refs: PR #3557 review comments r3594064534, r3594064563
Assisted-by: opencode-go/qwen3.7-max (autonomous)
* fix(workflows): address Copilot review findings in merge engine
- Apply inserts before winning replace to prevent anchor-not-found errors
when replace changes step ID (r3594064604)
- Track attribution recursively for nested steps in composite inserts/replaces
so workflow resolve attributes all child steps correctly (r3594064638)
- Add regression tests for both fixes
Refs: PR #3557 review discussion
Assisted-by: GitHub Copilot (model: qwen3.7-plus, autonomous)
* refactor(workflows): simplify overlay architecture to 2-tier
Remove installed overlays tier to enforce clean separation of concerns:
- workflow add installs workflows only (no overlay copying)
- workflow overlay add installs overlays only (project-local)
Changes:
- Remove InstalledOverlaySource class and all references
- Remove overlay-copying logic from _validate_and_install_local()
- Update WorkflowResolver to 2-tier: project overlays + base workflow
- Fix --priority override timing: apply before validation, not after
- Remove tests for installed overlays (no longer applicable)
Rationale: If upstream controls both base workflow and shipped overlays,
and both get overwritten on bundle update, there's no reason to ship
overlays separately. Overlays only make sense when someone other than
the base author adds them.
Resolves all three review findings from PR #3557:
- r3594064677: workflow add no longer copies overlays from all call sites
- r3594064705: --priority override now applied before validation
- r3594064726: no stale installed overlays (tier removed entirely)
Assisted-by: Claude (model: claude-opus-4-7, autonomous)
* fix(workflows): harden overlay symlink handling
Assisted-by: GitHub Copilot (model: GPT-5.4, autonomous)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs(workflows): remove stale installed-overlay references from workflows.md
The 2-tier refactor (cc28185) removed the installed-overlay tier entirely,
but docs/reference/workflows.md was not updated. This commit addresses all
four Cluster 2 findings from the PR review:
- workflow add: remove sentence about copying overlays/ subdirectory
- How Overlays Work: drop installed-overlay table row and precedence prose;
rewrite to 2-tier model (project overlays only, source-order tie-break)
- overlay remove: drop trailing sentence about installed overlays
- Interaction with Bundles: rewrite to say workflow add installs only
workflow.yml; remove installed-overlay discovery language
Fixes: r3596368791, r3596368831, r3596368873, r3596368919
Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(overlays): detect ancestor-conflict anchors in merge_steps
When two overlay edits target anchors that share a parent/descendant
relationship (e.g. remove an if-step + insert_after a nested child),
merge_steps processed them independently and in dict-insertion order,
making the outcome non-deterministic.
Add two private helpers to merge.py:
- _descendant_ids(step): returns all step IDs nested inside a step dict
by delegating to the existing _all_base_step_ids helper on children.
- _check_anchor_conflicts(anchors, base_steps): for each targeted anchor
finds its descendants and checks whether any other targeted anchor is
among them; returns human-readable error strings.
Wire _check_anchor_conflicts into merge_steps immediately after
edits_by_anchor is built, before any tree mutation occurs. Raises
ValueError listing the conflicting anchor pair(s) so overlay authors
know exactly what to fix.
Add TestMergeStepsAncestorConflicts (6 cases):
- remove parent + insert_after child raises ValueError
- replace parent + remove child raises ValueError
- conflict across multiple overlays raises ValueError
- sibling anchors (not ancestor/descendant) pass
- single anchor passes
- parent targeted but child not targeted passes
Closes review comment r3596368746 (PR #3557, round 2, cluster 3).
Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(overlays): fix over-broad conflict detection and non-deterministic ID collision
Finding 1.1 — _check_anchor_conflicts was rejecting any ancestor/descendant
anchor pair, including insert-only edits that are perfectly safe. Only
replace/remove on an ancestor can destroy its subtree and make a descendant
anchor unresolvable. Change the signature to accept a dict[str, str]
(anchor → winning operation) and skip the check for insert_after/insert_before.
Finding 1.2 — merge_steps was calling find_step on the already-mutated tree,
so a replacement step that reused a base step ID could be accidentally targeted
by a later edit group (non-deterministic result depending on dict iteration
order). Replace the anchor-group loop with a single-pass _traverse_and_apply
that walks the original tree structure and applies edits as each step is
encountered. Anchors are never re-looked up in a mutated tree.
Design invariant enforced: overlays always apply to the original base tree and
cannot target steps introduced by other overlays. Non-remove edits on non-base
anchors now raise ValueError early.
Also removes apply_edit (no production callers, only tested in isolation) and
its test class — the new traversal inlines the same mechanics without the
find_step round-trip.
Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(overlays): reject ID trailing newlines and reuse existing .yaml path
Fix two input validation bugs in the overlay layer (Group 2 of copilot
review PR #3557):
1. _validate_safe_id in schema.py used re.match() which anchors only at
the start of the string, so IDs like 'overlay\n' passed validation
and could produce newline-containing file paths. Changed to fullmatch()
so the entire string must satisfy the pattern.
2. workflow_overlay_add always wrote <id>.yml without checking whether
<id>.yaml already existed. Since the resolver loads both extensions,
this created two active layers whose edits applied twice. Now uses
the existing _find_overlay_file() to detect a pre-existing file and
reuse its path, falling back to .yml only for new overlays.
Tests added for both fixes.
Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(overlays): fix display order inversion and wrap file-read errors
Finding group 3 from copilot-review-v2.md:
3.1 — Precedence display inverted (overlays/__init__.py)
collect_all_layers used a single-pass sort by (-priority, source_asc),
which placed the *losing* equal-priority source first in the display
while claiming "highest first". Fix: two-pass stable sort — source
descending then priority descending — so the actual winner (last applied
by the composer) rises to the top of the display.
3.2 — Unwrapped file-read errors (overlays/layer_sources.py)
Only yaml.YAMLError was caught around path.read_text(), so an
unreadable or non-UTF-8 overlay produced a raw traceback. Fix: widen
the except clause to (yaml.YAMLError, OSError, UnicodeDecodeError),
matching the pattern used throughout catalog.py.
Tests:
- test_workflow_resolve_equal_priority_winner_shown_first: verifies
project:zzz (the winner) appears before project:aaa in workflow resolve
output when both overlays share the same priority.
- tests/workflows/test_overlay_layer_sources.py (new): OSError and
non-UTF-8 bytes both produce OverlayLoadError, not raw tracebacks.
Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: rename misleading overlay test
Assisted-by: GitHub Copilot (model: gpt-5.3-codex, autonomous)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: remove EOF blank line in overlay resolver
Assisted-by: GitHub Copilot (model: gpt-5.3-codex, autonomous)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* fix: handle overlay read and enumeration errors
Assisted-by: GitHub Copilot (model: gpt-5.3-codex, autonomous)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(overlays): validate resolver workflow IDs
Reject unsafe and reserved workflow IDs before overlay or base sources construct paths, preventing traversal through resolver and engine fallback paths.
Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* fix(overlays): drop _remove_sources_recursively from remove branch
In _traverse_and_apply, the remove branch called _remove_sources_recursively
to clean up attribution entries for the deleted step. This was inherited from
the old apply_edit loop (c70a5d6) where it was needed because the sources dict
was queried exhaustively.
In the current single-pass design, _build_attribution only traverses the result
list, so stale sources entries for removed steps are never read. The cleanup
call is therefore unnecessary — and actively harmful when another overlay has
replaced a different step with a new step that reuses the same ID: the pop
clobbers the replacement's attribution entry, causing workflow resolve to report
the surviving step as 'unknown'.
Fix: simply remove the _remove_sources_recursively call from the remove branch.
Add an attribution assertion to the existing reused-ID regression test to catch
this case.
Fixes: r3604242050 (Copilot review finding)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous)
* Fix CLI overlay ID validation anchoring
Use fullmatch for CLI workflow/overlay ID validation so trailing newlines are rejected consistently with manifest validation.
Add regression coverage for newline-suffixed workflow and overlay IDs in overlay set-priority.
Assisted-by: GitHub Copilot (model: gpt-5.3-codex, autonomous)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(workflows): validate workflow_id in layer sources before path construction
ProjectOverlaySource.collect() and BaseWorkflowSource.collect() joined
workflow_id directly onto storage paths without validation, enabling
path traversal (e.g. '../../outside') when called outside the
WorkflowResolver.
Add _validate_workflow_id() to layer_sources.py — mirrors the same
_SAFE_ID_PATTERN / _RESERVED_WORKFLOW_IDS check used by WorkflowResolver
in overlays/__init__.py — and call it at the top of both collect()
methods before any path is constructed.
Adds parametrised tests covering unsafe IDs and verifying no filesystem
access occurs for an invalid ID.
Closes review finding r3604772700.
Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(workflows): align layer source validation with _safe_workflow_id_dir
Plan §4.1 requires that Workflow-ID-Validierung, Symlink-/Containment-
Prüfungen and Fehlerübersetzung must not diverge between workflow
management and the overlay resolver.
My previous fix added ID pattern + reserved-name validation to both
collect() methods but was missing the containment step and the
BaseWorkflowSource directory/file checks that _safe_workflow_id_dir
performs.
Changes:
- Add _ensure_contained_dir(path, root) to layer_sources.py — pure
domain mirror of overlays/_commands.py::_ensure_contained_dir that
raises OverlayLoadError instead of typer.Exit
- ProjectOverlaySource.collect(): replace two inline symlink/dir checks
with _ensure_contained_dir(workflow_overlay_dir, self.overlays_dir),
adding the missing resolve().relative_to() containment step
- BaseWorkflowSource.collect(): add _ensure_contained_dir on the
workflow directory, and add workflow.yml symlink check before is_file()
The same logic now lives in three places (workflow CLI, overlay CLI,
layer sources). The DRY extraction to workflows/_validation.py is
deferred to PR 3 per plan §4.1.
Tests: add containment and symlink tests for both sources.
Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* fix(overlays): resolve identity from manifest field, not filename
Align overlay identity resolution with the project-wide convention:
presets use preset.id, extensions use extension.id, workflows use
workflow.id, and workflow steps use step.type_key. Overlays must
derive identity from the manifest id field, not the filename.
Rewrite _find_overlay_file() to scan all YAML files in the overlay
directory and match on the manifest id field, fixing the bug where
enable/disable/remove/set-priority failed when filename != manifest id.
Closes: PR #3557 discussion r3605010197
Assisted-by: opencode-go/qwen3.7-max (autonomous)
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* fix(workflows): make validation behavior consistent across YAML loading paths
Address PR #3557 review finding r3607632921:
- Wrap yaml.YAMLError → ValueError in from_yaml() and from_string() so
malformed YAML matches the documented exception contract
- Add except ValueError to workflow_info to handle composition errors
cleanly instead of crashing with a raw traceback
- Remove validate_workflow() from compose() so the resolver path is
parse-only like all other YAML loading mechanisms; callers validate
explicitly via engine.validate()
- Update test to reflect new behavior: resolve() returns composed
definition, caller validates separately
Assisted-by: opencode-go/qwen3.7-max (autonomous)
* fix(overlays): list disabled overlays in management view
Keep disabled overlays visible in workflow overlay list while leaving resolution behavior unchanged.
- add an include_disabled opt-in to overlay source/resolver collection
- use include_disabled=True for workflow overlay list
- add regression tests for list visibility and default filtering
Assisted-by: GitHub Copilot (model: GPT-5.4, autonomous)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* docs: align overlay extends and resolver contract
Assisted-by: GitHub Copilot (model: GPT-5.3-Codex, autonomous)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: use atomic write for overlay file updates to prevent hard-link attack
Replace in-place write_text() calls in workflow_overlay_add() and
_update_overlay_field() with the same mkstemp → write → os.replace()
pattern used by the workflow installer (_stage_workflow_file /
_commit_workflow_file / _discard_staged_workflow_file).
The prior code rejected symlinks and validated path containment, but a
hard-linked destination file passes both checks while sharing an inode
with an external file. write_text() would then truncate and overwrite
that external inode. The atomic staging approach never opens the
existing destination for writing, eliminating the hard-link vector.
Fixes findings r3608669512 and r3608669517 on PR #3557.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, supervised)
* fix(composer): preserve invalid base definition instead of coercing steps to []
When 'steps' is not a list, returning early with the unmodified
WorkflowDefinition lets validate_workflow surface the proper error
("'steps' must be a list.") to the caller. The previous silent
coercion to [] masked the validation error entirely.
Fixes: https://github.com/github/spec-kit/pull/3557#discussion_r3608669506
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, supervised)
* fix: align workflow overlay priority semantics
Assisted-by: GitHub Copilot (model: GPT-5.6 Terra, autonomous)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: validate overlay priority presentation
Assisted-by: GitHub Copilot (model: GPT-5.6 Terra, autonomous)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: catch OverflowError in normalize_priority for float infinity values
YAML values like `priority: .inf` parse to float('inf'), causing
int() to raise OverflowError. This broke validate_overlay_yaml()'s
'validation never raises' contract. Adding OverflowError to the
except clause makes it fall back to the default priority (10),
consistent with other invalid value handling.
Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Markus <markus@example.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* 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>
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>
* 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>
* 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>
* 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>
* 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>