Repository navigation
Conversation
… removed Removing a preset that is the base (or a middle layer) of another preset's prepend/append/wrap composition left the removed preset's content in the active agent's SKILL.md (Claude, Codex, Copilot with --skills, ...), while command-file agents such as Gemini were recomposed onto the new stack. The composed skill is owned by the surviving preset at the top of the chain, so _unregister_skills() restores nothing for the removed preset, and _reconcile_skills() only revisited the active skills directory when that restore had touched it. The recomposed .composed/<cmd>.md that _reconcile_composed_commands() writes was therefore never rendered into the skill, and `specify preset resolve` disagreed with what the agent ran. After a removal, also reconcile active-directory skills for commands whose winning layer composes, as long as the skill still carries the winning preset's source marker. A hand-edited skill is left alone, matching the existing protection for non-owned skills during removal. Assisted-by: Claude Code (model: Claude Opus 5.5, autonomous) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Removing a preset that is the base, or a middle layer, of another preset's
prepend/append/wrapcomposition leaves the removed preset's content in the active agent'sSKILL.md. That affects Claude, Codex, Copilot with--skills, and other skills-based integrations.specify preset resolvereports the new chain, and command-file agents such as Gemini are recomposed onto it. But the skill keeps the removed preset's instructions, so/speckit-specifyruns content the user just removed.Reproduction (
alphareplacesspeckit.specifywithALPHA BODY;betaappendsBETA APPENDED):The same steps with
--integration geminidropALPHA BODYfrom.gemini/commands/speckit.specify.tomlonmain, so only skills output is stale.Root cause, in
presets/_manager_skills.py:_reconcile_skills()only revisits the active skills directory when_unregister_skills()restored something there (if extra_skills_dirs is None or active_provenance).beta), not the removed one, so nothing is restored and the active directory is skipped..composed/<cmd>.mdthat_reconcile_composed_commands()writes is therefore never rendered into the skill.683bfd0) and moved in the refactor(presets): split domain internals into private modules #4747 refactor. Before fix: register extensions for the active integration only #3459 the active directory was always reconciled.The fix:
_reconcile_skills()now records commands whose winning layer composes (strategy other thanreplace).SKILL.mdstill carries the winning preset'smetadata.source: preset:<id>marker. A new helper,_skills_owned_by_preset(), does that check.Testing
uv run specify --helpuv sync && uv run pytestclaude(onmainand this branch) andgemini(onmain)New tests in
tests/specify_cli/presets/test_manager_skills.py:test_remove_base_preset_recomposes_surviving_composed_skill: fails onmainwithassert "Base preset body" not in contenttest_remove_middle_preset_drops_its_layer_from_composed_skill: fails onmainwithassert "Middle body" not in contenttest_remove_base_preset_preserves_hand_edited_composed_skill: the negative case, guarding the hand-edit protection; passes before and afterWith the change the file gives 70 passed.
tests/test_*_python_parity.pyfiles occur identically on unmodifiedmainon this machine under full-suite load; those files pass when run on their own (257 passed, 112 skipped).test_resolver.py::TestResolveCore::test_resolve_core_does_not_return_preset_files, was an artifact of a worktree directory literally namedpresets: the test asserts"presets" not in path.parts. It fails identically onmainin that directory and passes elsewhere.uvx ruff@0.15.0 check src tests: cleanAI Disclosure
AI disclosure: Claude Code (model: Claude Opus 5.5, max reasoning effort, autonomous agent mode) opened this PR on behalf of @yxshee. A Claude Code subagent (same model, autonomous) found the bug and wrote the fix and tests. The lead agent independently re-ran the new tests against
mainand the branch, reproduced the behavior with the real CLI (Claude and Gemini), confirmed the #3459 origin withgit log -S, reviewed the diff, and wrote this description. No human reviewed the change line by line before it was opened.🤖 Generated with Claude Code