mirror of
https://github.com/github/spec-kit.git
synced 2026-07-05 21:49:47 +08:00
424a14e6555a2b38cdca6be1a655d499a213e785
27 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
9732a4d092 |
fix(opencode): use commands/ directory (plural) to match OpenCode docs (#2453)
* fix(opencode): use commands/ directory (plural) to match OpenCode docs OpenCode documentation (https://opencode.ai/docs/commands/) uses .opencode/commands/ (plural) as the canonical command directory. The OpenCode runtime supports both .opencode/command/ and .opencode/commands/ via a {command,commands} glob, but the singular form was the original convention and is now outdated. Update the OpenCode integration to write to .opencode/commands/ instead of .opencode/command/, aligning with the documented standard and the OpenSpec fix (Fission-AI/OpenSpec#748). Signed-off-by: Marcus Burghardt <maburgha@redhat.com> Assisted-by: OpenCode (claude-opus-4-6) * feat(registrar): add legacy_dir fallback for backward-compatible directory migration Add _resolve_agent_dir() to CommandRegistrar that checks a legacy_dir fallback when the canonical directory does not exist. When legacy_dir is found, a deprecation warning directs users to run "specify integration upgrade" to migrate. The OpenCode integration declares legacy_dir: ".opencode/command" so that extension and preset registration, as well as command cleanup, continue working for projects that have not yet migrated to .opencode/commands/. The legacy_dir mechanism is opt-in: integrations that do not declare it get no fallback and no behavioral change. Add end-to-end test verifying that "specify integration upgrade opencode" migrates commands from legacy .opencode/command/ to canonical .opencode/commands/ and removes stale files. Signed-off-by: Marcus Burghardt <maburgha@redhat.com> Assisted-by: OpenCode (claude-opus-4-6) * fix(registrar): address PR review feedback on legacy_dir handling - Fix deprecation warning formatting: quote paths and remove trailing '/.' that produced confusing '.opencode/commands/.' output - Eliminate duplicate warnings: pass pre-resolved directory to register_commands() via _resolved_dir parameter so _resolve_agent_dir() is only called once per agent - Fix unregister_commands() to clean both canonical and legacy dirs when both exist, preventing orphaned command files after upgrade - Add test_unregister_cleans_legacy_when_both_dirs_exist regression test and tighten warning count assertion to exactly 1 Assisted-by: OpenCode (claude-opus-4-6) Signed-off-by: Marcus Burghardt <maburgha@redhat.com> --------- Signed-off-by: Marcus Burghardt <maburgha@redhat.com> |
||
|
|
793632089a |
fix(forge): use hyphen notation for command refs in Forge integration (#2462)
* fix(forge): use hyphen notation for command refs in Forge integration - Add invoke_separator = "-" class attribute to ForgeIntegration so effective_invoke_separator() returns "-" for shared-template installs - Add "invoke_separator": "-" to ForgeIntegration.registrar_config so agents.py CommandRegistrar can resolve refs with the correct separator - Pass invoke_separator to process_template() in ForgeIntegration.setup() so all .forge/commands/*.md bodies use /speckit-foo notation - Replace literal /speckit.specify with __SPECKIT_COMMAND_SPECIFY__ in extensions/git/commands/speckit.git.feature.md so every agent resolves the reference through its own separator - Apply resolve_command_refs re.sub in agents.py register_commands() after argument-placeholder substitution so extension commands registered for Forge get /speckit-foo refs; all other agents continue to get /speckit.foo Fixes ZSH compatibility: dot-notation command invocations (/speckit.specify) are misinterpreted by ZSH as file-path operations; hyphen notation (/speckit-specify) works correctly in all shells. * fix(agents): propagate invoke_separator from integration class into AGENT_CONFIGS Skills-based agents (claude, codex, kimi, …) inherit invoke_separator="-" from SkillsIntegration but do not repeat it in their registrar_config dicts. _build_agent_configs() was copying registrar_config verbatim, so register_commands() fell back to "." when resolving __SPECKIT_COMMAND_*__ tokens for those agents — emitting /speckit.specify instead of the correct /speckit-specify for extension commands like speckit.git.feature. Fix: after copying registrar_config, inject invoke_separator from the integration's class attribute when it is not already declared explicitly. This makes the integration class the single source of truth for all agents, without requiring each SkillsIntegration subclass to duplicate the field. Also replace the inline re.sub in register_commands() with a call to IntegrationBase.resolve_command_refs() (deferred import to avoid the existing circular dependency) so token-resolution logic is not duplicated. Adds two tests in test_agent_config_consistency.py: - test_skills_agents_have_hyphen_invoke_separator_in_agent_configs: asserts every /SKILL.md agent has invoke_separator="-" in AGENT_CONFIGS. - test_skills_agent_command_token_resolves_with_hyphen: end-to-end check via CommandRegistrar that the git extension's speckit.git.feature command is installed for Claude with /speckit-specify (not /speckit.specify). Addresses review comment on PR #2462. |
||
|
|
a067d4c2e3 |
feat(presets): Composition strategies (prepend, append, wrap) for templates, commands, and scripts (#2133)
* fix: rebase onto upstream/main, resolve conflicts with PR #2189 upstream/main merged PR #2189 (wrap-only strategy) which overlaps with our comprehensive composition strategies (prepend/append/wrap). Resolved conflicts keeping our implementation as source of truth: - README: keep our future considerations (composition is now fully implemented, not a future item) - presets.py: keep our composition architecture (_reconcile_composed_commands, collect_all_layers, resolve_content) while preserving #2189's _substitute_core_template which is used by agents.py for skill generation - tests: keep both test sets (our composition tests + #2189's wrap tests), removed TestReplayWrapsForCommand and TestInstallRemoveWrapLifecycle which test the superseded _replay_wraps_for_command API; our composition tests cover equivalent scenarios - Restored missing _unregister_commands call in remove() that was lost during #2189 merge * fix: re-create skill directory in _reconcile_skills after removal After _unregister_skills removes a skill directory, _register_skills skips writing because the dir no longer passes the is_dir() check. Fix by ensuring the skill subdirectory exists before calling _register_skills so the next winning preset's content gets registered. Fixes the Claude E2E failure where removing a top-priority override preset left skill-based agents without any SKILL.md file. * fix: address twenty-third round of Copilot PR review feedback - Protect reconciliation in remove(): wrap _reconcile_composed_commands and _reconcile_skills in try/except so failures emit a warning instead of leaving the project in an inconsistent state - Protect reconciliation in install(): same pattern for post-install reconciliation so partial installs don't lack cleanup - Inherit scripts/agent_scripts from base frontmatter: when composing commands, merge scripts and agent_scripts keys from the base command's frontmatter into the top layer's frontmatter if missing, preventing composed commands from losing required script references - Add tier-5 bundled core fallback to collect_all_layers(): check the bundled core_pack (wheel) or repo-root templates (source checkout) when .specify/templates/ doesn't contain the core file, matching resolve()'s tier-5 fallback so composition can always find a base layer * fix: address twenty-fourth round of Copilot PR review feedback - Use yaml.safe_load for frontmatter parsing in resolve_content instead of CommandRegistrar.parse_frontmatter which uses naive find('---',3); strip strategy key from final frontmatter to prevent leaking internal composition directives into rendered agent command files - Filter _reconcile_skills to specific commands: use _FilteredManifest wrapper so only the commands being reconciled get their skills updated, preventing accidental overwrites of other commands' skills that may be owned by higher-priority presets * fix: address twenty-fifth round of Copilot PR review feedback - Support legacy command-frontmatter strategy: when preset.yml doesn't declare a strategy, check the command file's YAML frontmatter for strategy: wrap as a fallback so legacy wrap presets participate in composition and multi-preset chaining - Guard skill dir creation in _reconcile_skills: only re-create the skill directory if the skill was previously managed (listed in some preset's registered_skills), avoiding creation of new skill dirs that _register_skills would normally skip * fix: add explanatory comment to empty except in legacy frontmatter parsing * fix: address twenty-sixth round of Copilot PR review feedback - Unregister stale commands when composition fails: when resolve_content returns None during reconciliation (base layer removed), unregister the command from non-skill agents and emit a warning - Load extension aliases during reconciliation: _register_command_from_path now checks extension.yml for aliases when the winning layer is an extension, so alias files are restored after preset removal - Use line-based fence detection for legacy frontmatter strategy fallback: scan for --- on its own line instead of split('---',2) to avoid mis-parsing YAML values containing --- * fix: address twenty-seventh round of Copilot PR review feedback - Handle non-preset winners in _reconcile_skills: when the winning layer is core/extension/project-override, restore skills via _unregister_skills so skill-based agents stay consistent with the priority stack - Update base_frontmatter_text on replace layers: when a higher-priority replace layer occurs during composition, update both top and base frontmatter so scripts/agent_scripts inheritance reflects the effective base beneath the top composed layer * fix: address twenty-eighth round of Copilot PR review feedback - Parse only interior lines in _parse_fm_yaml: use lines[1:-1] instead of filtering all --- lines, preventing corruption when YAML values contain a line that is exactly --- - Omit empty frontmatter: skip re-rendering when top_fm is empty dict to avoid emitting ---/{}/--- for intentionally empty frontmatter - Update scaffold wrap comment: mention both {CORE_TEMPLATE} and $CORE_SCRIPT placeholders for templates/commands vs scripts - Clarify shell composition scope in ARCHITECTURE.md: note that bash/PS1 resolve_template_content only handles templates; command/script composition is handled by the Python resolver * fix: address twenty-ninth round of Copilot PR review feedback - Fix TestCollectAllLayers docstring: reference collect_all_layers() - Add default/unknown strategy handling in bash/PS1 composition: error on unrecognized strategy values instead of silently skipping - Fix comment: .composed/ is a persistent dir, not temporary - Fix comment: legacy fallback checks all valid strategies, not just wrap - Cache PresetRegistry in _reconcile_skills: build presets_by_priority once instead of constructing registry per-command * fix: address thirtieth round of Copilot PR review feedback - Guard legacy frontmatter fallback: only check command file frontmatter for strategy when the manifest entry doesn't explicitly include the strategy key, preventing override of manifest-declared strategies - Document rollback limitation: note that mid-registration failures may leave orphaned agent command files since partial progress isn't captured by the local vars * fix: handle project override skills and extension context in reconciliation * fix: add comment to empty except in extension registration fallback * fix: filter extension commands in reconciliation and fix type annotation * fix: filter extension commands from post-install reconciliation Apply the same extension-installed check used in _register_commands to the reconciliation command list, preventing reconciliation from registering commands for extensions that are not installed. * fix: skip convention fallback for explicit file paths and add stem fallback to tier-5 When a preset manifest provides an explicit file path that does not exist, skip the convention-based fallback to avoid masking typos. Also add speckit.<stem> to <stem>.md fallback in tier-5 bundled/source core lookup for consistency with tier-4. * fix: scan past non-replace layers to find base in resolve_content The base-finding scan now skips non-replace layers below a replace layer instead of stopping at the first non-replace. This fixes the case where a low-priority append/prepend layer sits below a replace that should serve as the base for composition. * fix: add context_note to non-skill agent registration for extensions Add context_note parameter to register_commands_for_non_skill_agents and pass extension name/id during reconciliation so rendered command files preserve the extension-specific context markers. * fix: Optional type, rollback safety, and override skill restoration - Fix context_note type to Optional[str] - Wrap shutil.rmtree in try/except during install rollback - Separate override-backed skills from core/extension in _reconcile_skills * fix: align bash/PS1 base-finding with Python resolver Rewrite bash and PowerShell composition loops to find the effective base replace layer first (scanning bottom-up, skipping non-replace layers below it), then compose only from the base upward. This prevents evaluation of irrelevant lower layers (e.g. a wrap with no placeholder below a replace) and matches resolve_content behavior. * fix: PS1 no-python warning, integration hook for override skills, alias cleanup - Warn when no Python 3 found in PS1 and presets use composition strategies - Apply post_process_skill_content integration hook when restoring override-backed skills so agent-specific flags are preserved - Unregister command aliases alongside primary name when composition fails to prevent orphaned alias files * fix: include aliases in removed_cmd_names during preset removal Read aliases from preset manifest before deleting pack_dir so alias command files are included in unregistration and reconciliation. * fix: add comment to empty except in alias extraction during removal * fix: scan top-down for effective base in all resolvers Change base-finding to scan from highest priority downward to find the nearest replace layer, then compose only layers above it. Prevents evaluation of irrelevant lower layers (e.g. a wrap without placeholder below a higher-priority replace) across Python, bash, and PowerShell. * fix: align CLI composition chain display with top-down base-finding Show only contributing layers (base and above) in preset resolve output, matching resolve_content top-down semantics. Layers below the effective base are omitted since they do not contribute. * fix: guard corrupted registry entries and make manifest authoritative - Add isinstance(meta, dict) guard in bash registry parsing so corrupted entries are skipped instead of breaking priority ordering - Only use convention-based file lookup when the manifest does not list the requested template, making preset.yml authoritative and preventing stray on-disk files from creating unintended layers * fix: align resolve() with manifest file paths and match extension context_note - Update resolve() preset tier to consult manifest file paths before convention-based lookup, matching collect_all_layers behavior - Use exact extension context_note format matching extensions.CommandRegistrar - Update test to declare template in manifest (authoritative manifest) * revert: restore resolve() convention-based behavior for backwards compatibility resolve() is the existing public API used by shell scripts and other callers. Changing it to manifest-authoritative breaks backward compat for presets that rely on convention-based file lookup. Only the new collect_all_layers/resolve_content path uses manifest-authoritative logic. * fix: only pre-compose when this preset is the top composing layer Skip composition in _register_commands when a higher-priority replace layer already wins for the command. Register the raw file instead and let reconciliation write the correct final content. * fix: deduplicate PyYAML warnings and use self.registry in reconciliation - Emit PyYAML-missing warning once per function call in bash/PS1 instead of per-preset to avoid spamming stderr - Use self.registry.list_by_priority() in reconciliation methods instead of constructing new PresetRegistry instances to avoid redundant I/O and potential consistency issues * fix: document strategy handling consistency between layers and registrar Composed output already strips strategy from frontmatter (resolve_content pops it). Raw file registration preserves legacy frontmatter strategy for backward compat; reconciliation corrects the final state. * fix: correct stale comments for alias tracking and base-finding algorithm * security: validate manifest file paths in bash/PowerShell resolvers Reject absolute paths and parent directory traversal (..) in the manifest-declared file field before joining with the preset directory. Matches the Python-side validation in PresetManifest._validate(). --------- Co-authored-by: Manfred Riem <15701806+mnriem@users.noreply.github.com> |
||
|
|
ecb3b94b43 |
fix: resolve skill placeholders for all SKILL.md agents, not just codex/kimi (#2313)
* fix: resolve skill placeholders for all SKILL.md agents, not just codex/kimi * chore: remove unused NATIVE_SKILLS_AGENTS constant |
||
|
|
22e76995c7 |
feat: implement preset wrap strategy (#2189)
* feat: implement strategy: wrap * fix: resolve merge conflict for strategy wrap correctness * feat: multi-preset composable wrapping with priority ordering Implements comment #4 from PR review: multiple installed wrap presets now compose in priority order rather than overwriting each other. Key changes: - PresetResolver.resolve() gains skip_presets flag; resolve_core() wraps it to skip tier 2, preventing accidental nesting during replay - _replay_wraps_for_command() recomposed all enabled wrap presets for a command in ascending priority order (innermost-first) after any install or remove - _replay_skill_override() keeps SKILL.md in sync with the recomposed command body for ai-skills-enabled projects - install_from_directory() detects strategy: wrap commands, stores wrap_commands in the registry entry, and calls replay after install - remove() reads wrap_commands before deletion, removes registry entry before rmtree so replay sees post-removal state, then replays remaining wraps or unregisters when none remain Tests: TestResolveCore (5), TestReplayWrapsForCommand (5), TestInstallRemoveWrapLifecycle (5), plus 2 skill/alias regression tests * fix: resolve extension commands via manifest file mapping PresetResolver.resolve_extension_command_via_manifest() consults each installed extension.yml to find the actual file declared for a command name, rather than assuming the file is named <cmd_name>.md. This fixes _substitute_core_template for extensions like selftest where the manifest maps speckit.selftest.extension → commands/selftest.md. Resolution order in _substitute_core_template is now: 1. resolve_core(cmd_name) — project overrides win, then name-based lookup 2. resolve_extension_command_via_manifest(cmd_name) — manifest fallback 3. resolve_core(short_name) — core template short-name fallback Path traversal guard mirrors the containment check already present in ExtensionManager to reject absolute paths or paths escaping the extension root. * fix: add bundled core_pack as Priority 5 in PresetResolver.resolve() resolve_core() was returning None for built-in commands (implement, specify, etc.) because PresetResolver only checked .specify/templates/ commands/ (Priority 4), which is never populated for commands in a normal project. strategy:wrap presets rely on resolve_core() to fetch the {CORE_TEMPLATE} body, so the wrap was silently skipped and SKILL.md was never updated. Priority 5 now checks core_pack/commands/ (wheel install) or repo_root/templates/commands/ (source checkout), mirroring the pattern used by _locate_core_pack() elsewhere. Updated two tests whose assertions assumed resolve_core() always returned None when .specify/templates/commands/ was absent. * fix: harden preset wrap replay removal * fix: stabilize existing directory error output * fix: track outermost_pack_id from contributing preset; use Path.parts in tests - outermost_pack_id now updates alongside outermost_frontmatter inside the wrap loop, so it reflects the actual last contributing preset rather than always taking wrap_presets[0] (which may have been skipped) - Replace str(path) substring checks in TestResolveCore with Path.parts tuple comparisons for correct behaviour on Windows (CI runs windows-latest) * fix: guard against non-mapping YAML manifests; apply integration post-processing in replay - ExtensionManifest._load raises ValidationError for non-dict YAML roots instead of TypeError - PresetManager._replay_wraps_for_command calls integration.post_process_skill_content, matching _register_skills behaviour - PresetResolver skips extensions that raise OSError/TypeError/AttributeError on manifest load - Tests: non-mapping YAML, OSError manifest skip, and replay integration post-processing |
||
|
|
569d18a59d |
fix(agents): block directory traversal in command write paths (#2229) (#2296)
Extend the alias containment guard from
|
||
|
|
fc3d1244c0 |
fix: replace shell-based context updates with marker-based upsert (#2259)
* Replace shell-based context updates with marker-based upsert
Replace ~3500 lines of bash/PowerShell agent context update scripts
with a Python-based approach using <!-- SPECKIT START/END --> markers.
IntegrationBase now manages the agent context file directly:
- upsert_context_section(): creates or updates the marked section at
init/install/switch time with a directive to read the current plan
- remove_context_section(): removes the section at uninstall, deleting
the file only if it becomes empty
- __CONTEXT_FILE__ placeholder in command templates is resolved per
integration so the plan command references the correct agent file
- context_file is persisted in init-options.json for extension access
The plan command template instructs the LLM to update the plan
reference between the markers in the agent context file.
Removed:
- scripts/bash/update-agent-context.sh (857 lines)
- scripts/powershell/update-agent-context.ps1 (515 lines)
- 56 integration wrapper scripts (update-context.sh/.ps1)
- templates/agent-file-template.md
- agent_scripts frontmatter key and {AGENT_SCRIPT} replacement logic
- update-context reference from integration.json
- tests/test_cursor_frontmatter.py (tested deleted scripts)
Added:
- upsert/remove context section methods on IntegrationBase
- __CONTEXT_FILE__ placeholder support in process_template()
- context_file field in init-options.json (init/switch/uninstall)
- Per-integration tests: context file correctness, plan reference,
init-options persistence (78 new context_file tests)
- End-to-end CLI validation across all 28 integrations
* fix: search for end marker after start marker in context section methods
Address Copilot review: content.find(CONTEXT_MARKER_END) searched from
the start of the file rather than after the located start marker. If
the file contained a stray end marker before the start marker, the
wrong slice could be replaced.
Now both upsert_context_section() and remove_context_section() pass
start_idx as the second argument to find() and validate end_idx >
start_idx before performing the replacement.
* fix: address Copilot review feedback on context section handling
1. Fix grammar in _build_context_section() directive text — add commas
for a complete sentence.
2. Resolve __CONTEXT_FILE__ in resolve_skill_placeholders() — skills
generated via extensions/presets for codex/kimi now replace the
placeholder using the context_file value from init-options.json.
3. Handle Cursor .mdc frontmatter — when creating a new .mdc context
file, prepend alwaysApply: true YAML frontmatter so Cursor
auto-loads the rules.
4. Fix empty-file leading newline — when the context file exists but
is empty, write the section directly instead of prepending a blank
line.
* fix: address second round of Copilot review feedback
1. Ensure .mdc frontmatter on existing files — upsert_context_section()
now checks for missing YAML frontmatter on .mdc files during updates
(not just creation), so pre-existing Cursor files get alwaysApply.
2. Guard against context_file=None — use 'or ""' instead of a default
arg so explicit null values in init-options.json don't cause a
TypeError in str.replace().
3. Clean up .mdc files on removal — remove_context_section() treats
files containing only the Speckit-generated frontmatter block as
empty, deleting them rather than leaving orphaned frontmatter.
* fix: address third round of Copilot review feedback
1. CRLF-safe .mdc frontmatter check — use lstrip().startswith('---')
instead of startswith('---\n') so CRLF files don't get duplicate
frontmatter.
2. CRLF-safe .mdc removal check — normalize line endings before
comparing against the sentinel frontmatter string.
3. Call remove_context_section() during integration_uninstall() — the
manifest-only uninstall was leaving the managed SPECKIT markers
behind in the agent context file.
4. Fix stale docstring — remove 'agent_scripts' mention from
test_lean_commands_have_no_scripts().
* fix: address fourth round of Copilot review feedback
1. Remove unused script_type parameter from _write_integration_json()
and all 3 call sites — the parameter was no longer referenced after
the update-context script removal.
2. Fix _build_context_section() docstring — correct example path from
'.specify/plans/plan.md' to 'specs/<feature>/plan.md'.
3. Improve .mdc frontmatter-only detection in remove_context_section()
— use regex to match any YAML frontmatter block (not just the exact
Speckit-generated one), so .mdc files with additional frontmatter
keys are also cleaned up when no body content remains.
* fix: handle corrupted markers and parse .mdc frontmatter robustly
1. Handle partial/corrupted markers in upsert_context_section() —
if only the START marker exists (no END), replace from START
through EOF. If only the END marker exists, replace from BOF
through END. This keeps upsert idempotent even when a user
accidentally deletes one marker.
2. Parse .mdc YAML frontmatter properly — new _ensure_mdc_frontmatter()
helper parses existing frontmatter and ensures alwaysApply: true is
set, rather than just checking for the --- delimiter. Handles
missing frontmatter, existing frontmatter without alwaysApply, and
already-correct frontmatter.
* fix: preserve .mdc frontmatter, add tests, clean up on switch
1. Rewrite _ensure_mdc_frontmatter() with regex — preserves comments,
formatting, and custom keys in existing frontmatter instead of
destructively re-serializing via yaml.safe_dump(). Inserts or
fixes alwaysApply: true in place.
2. Add 6 focused .mdc frontmatter tests to cursor-agent test file:
new file creation, missing frontmatter, preserved custom keys,
wrong alwaysApply value, idempotent upserts, removal cleanup.
3. Call remove_context_section() during integration switch Phase 1 —
prevents stale SPECKIT markers from being left in the old
integration's context file. Also clear context_file from
init-options during the metadata reset.
* fix: remove unused MDC_FRONTMATTER, preserve inline comments, normalize bare CR
1. Remove unused MDC_FRONTMATTER class variable — dead code after
_ensure_mdc_frontmatter() was rewritten with regex.
2. Preserve inline comments when fixing alwaysApply — the regex
substitution now captures trailing '# comment' text and keeps it.
3. Normalize bare CR in upsert_context_section() — match the
behavior of remove_context_section() which already normalizes
both CRLF and bare CR.
4. Clarify .mdc removal comment — 'treat frontmatter-only as empty'
instead of misleading 'strip frontmatter'.
* fix: handle corrupted markers in remove, CRLF-safe end-marker consumption
1. Handle corrupted markers in remove_context_section() — mirror
upsert's behavior: start-only removes start→EOF, end-only removes
BOF→end. Previously bailed out leaving partial markers behind.
2. CRLF-safe end-marker consumption — both upsert and remove now
handle \r\n after the end marker, not just \n. Prevents extra
blank lines at replacement boundaries in CRLF files.
3. Clarify path rule in plan template — distinguish filesystem
operations (absolute paths) from documentation/agent context
references (project-relative paths).
* fix: only remove context section when both markers are well-ordered
remove_context_section() previously treated mismatched markers as
corruption and aggressively removed from BOF→end-marker or
start-marker→EOF, which could delete user-authored content if only
one marker remained. Now it only removes when both START and END
markers exist and are properly ordered, returning False otherwise.
|
||
|
|
8fc2bd3489 |
fix: allow Claude to chain skills for hook execution (#2227)
* fix: allow Claude to chain skills for hook execution (#2178) - Set disable-model-invocation to false so Claude can invoke extension skills (e.g. speckit-git-feature) from within workflow skills - Inject dot-to-hyphen normalization note into Claude SKILL.md hook sections so the model maps extension.yml command names to skill names - Replace Unicode checkmark with ASCII [OK] in auto-commit scripts to fix PowerShell encoding errors on Windows - Move Claude-specific frontmatter injection to ClaudeIntegration via post_process_skill_content() hook on SkillsIntegration, wired through presets and extensions managers - Add positive and negative tests for all changes Fixes #2178 * refactor: address PR review feedback - Preserve line-ending style (CRLF/LF) in _inject_hook_command_note instead of always inserting \n, matching the convention used by other injection helpers in the same module. - Extract duplicated _post_process_skill() from extensions.py and presets.py into a shared post_process_skill() function in agents.py. Both modules now import and call the shared helper. * fix: match full hook instruction line in regex The regex in _inject_hook_command_note only matched lines ending immediately after 'output the following', but the actual template lines continue with 'based on its `optional` flag:'. Use [^\r\n]* to capture the rest of the line before the EOL. * refactor: use integration object directly for post_process_skill_content Instead of a free function in agents.py that re-resolves the integration by key, callers in extensions.py and presets.py now resolve the integration once via get_integration() and call integration.post_process_skill_content() directly. The base identity method lives on SkillsIntegration. |
||
|
|
33a28ec8f7 |
fix: unofficial PyPI warning (#1982) and legacy extension command name auto-correction (#2017) (#2027)
* docs: warn about unofficial PyPI packages and recommend version verification (#1982)
Clarify that only packages from github/spec-kit are official, and add
`specify version` as a post-install verification step to help users
catch accidental installation of an unrelated package with the same name.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(extensions): auto-correct legacy command names instead of hard-failing (#2017)
Community extensions that predate the strict naming requirement use two
common legacy formats ('speckit.command' and 'extension.command').
Instead of rejecting them outright, auto-correct to the required
'speckit.{extension}.{command}' pattern and emit a compatibility warning
so authors know they need to update their manifest. Names that cannot be
safely corrected (e.g. single-segment names) still raise ValidationError.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(tests): isolate preset catalog search test from community catalog network calls
test_search_with_cached_data asserted exactly 2 results but was getting 4
because _get_merged_packs() queries the full built-in catalog stack
(default + community). The community catalog had no local cache and hit
the network, returning real presets. Writing a project-level
preset-catalogs.yml that pins the test to the default URL only makes
the count assertions deterministic.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(extensions): extend auto-correction to aliases (#2017)
The upstream #1994 added alias validation in _collect_manifest_command_names,
which also rejected legacy 2-part alias names (e.g. 'speckit.verify').
Extend the same auto-correction logic from _validate() to cover aliases,
so both 'speckit.command' and 'extension.command' alias formats are
corrected to 'speckit.{ext_id}.{command}' with a compatibility warning
instead of hard-failing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(extensions): address PR review feedback (#2017)
- _try_correct_command_name: only correct 'X.Y' to 'speckit.ext_id.Y'
when X matches ext_id, preventing misleading warnings followed by
install failure due to namespace mismatch
- _validate: add aliases type/string guards matching _collect_manifest
_command_names defensive checks
- _validate: track command renames and rewrite any hook.*.command
references that pointed at a renamed command, emitting a warning
- test: fix test_command_name_autocorrect_no_speckit_prefix to use
ext_id matching the legacy namespace; add namespace-mismatch test
- test: replace redundant preset-catalogs.yml isolation with
monkeypatch.delenv("SPECKIT_PRESET_CATALOG_URL") so the env var
cannot bypass catalog restriction in CI environments
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update docs/installation.md
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* fix(extensions): warn when hook command refs are silently canonicalized; fix grammar
- Hook rewrites (alias-form or rename-map) now always emit a warning so
extension authors know to update their manifests. Previously only
rename-map rewrites produced a warning; pure alias-form lifts were
silent.
- Pluralize "command/commands" in the uninstall confirmation message so
single-command extensions no longer print "1 commands".
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(extensions): raise ValidationError for non-dict hook entries
Silently skipping non-dict hook entries left them in manifest.hooks,
causing HookExecutor.register_hooks() to crash with AttributeError
when it called hook_config.get() on a non-mapping value.
Also updates PR description to accurately reflect the implementation
(no separate _try_correct_alias_name helper; aliases use the same
_try_correct_command_name path).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(extensions): derive remove cmd_count from registry, fix wording
Previously cmd_count used len(ext_manifest.commands) which only counted
primary commands and missed aliases. The registry's registered_commands
already tracks every command name (primaries + aliases) per agent, so
max(len(v) for v in registered_commands.values()) gives the correct
total.
Also changes "from AI agent" → "across AI agents" since remove()
unregisters commands from all detected agents.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(extensions): distinguish missing vs empty registered_commands in remove prompt
Using get() without a default lets us tell apart:
- key missing (legacy registry entry) → fall back to manifest count
- key present but empty dict (installed with no agent dirs) → show 0
Previously the truthiness check `if registered_commands and ...` treated
both cases the same, so an empty dict fell back to len(manifest.commands)
and overcounted commands that would actually be removed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(extensions): clarify removal prompt wording to 'per agent'
'across AI agents' implied a total count, but cmd_count uses max()
across agents (per-agent count). Using sum() would double-count since
users think in logical commands, not per-agent files. 'per agent'
accurately describes what the number represents.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(extensions): clarify cmd_count comment — per-agent max, not total
The comment said 'covers all agents' implying a total, but cmd_count uses
max() across agents (per-agent count). Updated comment to explain the
max() choice and why sum() would double-count.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(extensions): add CLI tests for remove confirmation pluralization
Adds TestExtensionRemoveCLI with two CliRunner tests:
- singular: 1 registered command → '1 command per agent'
- plural: 2 registered commands → '2 commands per agent'
These prevent regressions on the cmd_count pluralization logic
and the 'per agent' wording introduced in this PR.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(agents): remove orphaned SKILL.md parent dirs on unregister
For SKILL.md-based agents (codex, kimi), each command lives in its own
subdirectory (e.g. .agents/skills/speckit-ext-cmd/SKILL.md). The previous
unregister_commands() only unlinked the file, leaving an empty parent dir.
Now attempts rmdir() on the parent when it differs from the agent commands
dir. OSError is silenced so non-empty dirs (e.g. user files) are safely left.
Adds test_unregister_skill_removes_parent_directory to cover this.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(extensions): drop alias pattern enforcement from _validate()
Aliases are intentionally free-form to preserve community extension
compatibility (e.g. 'speckit.verify' short aliases used by spec-kit-verify
and other existing extensions). This aligns _validate() with the intent of
upstream commit
|
||
|
|
b67b2856b1 |
feat(agents): add Goose AI agent support (#2015)
* feat(integrations): add YamlIntegration base class for YAML recipe agents Signed-off-by: Furkan Köykıran <furkankoykiran@gmail.com> * feat(integrations): add Goose integration subpackage with YAML recipe support Signed-off-by: Furkan Köykıran <furkankoykiran@gmail.com> * feat(integrations): register GooseIntegration in the integration registry Signed-off-by: Furkan Köykıran <furkankoykiran@gmail.com> * feat(agents): add YAML format support to CommandRegistrar for extension/preset commands Signed-off-by: Furkan Köykıran <furkankoykiran@gmail.com> * feat(scripts): add goose agent type to bash update-agent-context script Signed-off-by: Furkan Köykıran <furkankoykiran@gmail.com> * feat(scripts): add goose agent type to PowerShell update-agent-context script Signed-off-by: Furkan Köykıran <furkankoykiran@gmail.com> * docs(agents): add Goose to supported agents table and integration notes Signed-off-by: Furkan Köykıran <furkankoykiran@gmail.com> * docs(readme): add Goose to supported agents table Signed-off-by: Furkan Köykıran <furkankoykiran@gmail.com> * test(integrations): add YamlIntegrationTests base mixin for YAML agent testing Signed-off-by: Furkan Köykıran <furkankoykiran@gmail.com> * test(integrations): add Goose integration tests Signed-off-by: Furkan Köykıran <furkankoykiran@gmail.com> * test(consistency): add Goose consistency checks for config, registrar, and scripts Signed-off-by: Furkan Köykıran <furkankoykiran@gmail.com> * docs(agents): move Goose to YAML Format section in Command File Formats Goose uses YAML recipes, not Markdown. Remove it from the Markdown Format list and add a dedicated YAML Format subsection with a representative recipe example showing prompt: | and {{args}} placeholders. * refactor(agents): delegate render_yaml_command to YamlIntegration Remove the duplicate header dict, yaml.safe_dump call, body indentation, and _human_title logic from CommandRegistrar.render_yaml_command(). Delegate to YamlIntegration._render_yaml() and _human_title() so YAML recipe output stays consistent across the init-time generation and command-registration code paths. * fix(agents): guard alias output path against directory traversal Validate that alias_file resolves within commands_dir before writing. Uses the same resolve().relative_to() pattern already established in extensions.py for ZIP path containment checks. * docs(agents): add Goose to Multi-Agent Support comment list in update-agent-context.sh * fix(agents): add goose to print_summary Usage line in bash context script The print_summary() function listed all supported agents in its Usage output but omitted goose, making it inconsistent with the header docs and the error message in update_specific_agent(). Signed-off-by: Furkan Köykıran <furkankoykiran@gmail.com> * fix(agents): add goose to Print-Summary Usage line in PowerShell context script The Print-Summary function listed all supported agents in its Usage output but omitted goose, making it inconsistent with the ValidateSet and the header documentation. Signed-off-by: Furkan Köykıran <furkankoykiran@gmail.com> * fix(agents): normalize description and title types in YamlIntegration.setup() YAML frontmatter can contain non-string types (null, list, int). Add isinstance checks matching TomlIntegration._extract_description() to ensure Goose recipes always receive valid string fields. Signed-off-by: Furkan Köykıran <furkankoykiran@gmail.com> * fix(agents): validate shared script exists before exec in Goose bash wrapper Add Forge-style check that the shared update-agent-context.sh is present and executable, producing a clear error instead of a cryptic shell exec failure when the shared script is missing. Signed-off-by: Furkan Köykıran <furkankoykiran@gmail.com> * fix(agents): validate shared script exists before invoke in Goose PowerShell wrapper Add Forge-style Test-Path check that the shared update-agent-context.ps1 exists, producing a clear error instead of a cryptic PowerShell failure when the shared script is missing. Signed-off-by: Furkan Köykıran <furkankoykiran@gmail.com> * fix(agents): normalize title and description types in render_yaml_command() Extension/preset frontmatter can contain non-string types. Add isinstance checks matching the normalization in YamlIntegration.setup() so both code paths produce valid Goose recipe fields. Signed-off-by: Furkan Köykıran <furkankoykiran@gmail.com> * fix(agents): replace $ARGUMENTS with arg_placeholder in process_template() Signed-off-by: Furkan Köykıran <furkankoykiran@gmail.com> * test(agents): assert $ARGUMENTS absent from generated YAML recipes Signed-off-by: Furkan Köykıran <furkankoykiran@gmail.com> * test(agents): assert $ARGUMENTS absent from generated TOML commands Signed-off-by: Furkan Köykıran <furkankoykiran@gmail.com> * fix(tests): rewrite docstring to avoid embedded triple-quote in TOML test Signed-off-by: Furkan Köykıran <furkankoykiran@gmail.com> --------- Signed-off-by: Furkan Köykıran <furkankoykiran@gmail.com> |
||
|
|
71143598be |
fix(forge): use hyphen notation in frontmatter name field (#2075)
* fix(forge): use hyphen notation in frontmatter name field
- Changed injected name field from 'speckit.{command}' to 'speckit-{command}'
- Keeps standard filename format 'speckit.{command}.md'
- Aligns with Forge's command naming convention requirements
- All tests pass
* feat(forge): centralize name formatting to fix extension/preset command names
Address PR feedback by centralizing Forge command name formatting to ensure
consistent hyphenated names across both core template setup and
extension/preset command registration.
Changes:
- Add format_forge_command_name() utility function in forge integration
- Update ForgeIntegration._apply_forge_transformations() to use centralized formatter
- Add _format_name_for_agent() helper in CommandRegistrar to apply agent-specific formatting
- Update CommandRegistrar.register_commands() to format names for Forge (both primary commands and aliases)
- Add comprehensive test coverage for the formatter and registrar behavior
Impact:
- Extension commands installed for Forge now use 'name: speckit-my-extension-example'
instead of 'name: speckit.my-extension.example'
- Fixes ZSH/shell compatibility issues with dot notation in command names
- Maintains backward compatibility for all other agents (they continue using dot notation)
- Eliminates duplication between integration setup and registrar paths
Example transformation:
Before: name: speckit.jira.sync-status (breaks in ZSH/Forge)
After: name: speckit-jira-sync-status (works everywhere)
Fixes inconsistency where core templates used hyphens but extension/preset
commands preserved dots, breaking Forge's naming requirements.
* refactor(forge): move name formatting logic to integration module
Move _format_name_for_agent function logic into Forge integration's
registrar_config as a 'format_name' callback, improving separation of
concerns and keeping Forge-specific logic within its integration module.
Changes:
- Remove _format_name_for_agent() from agents.py (shared module)
- Add 'format_name' callback to Forge's registrar_config pointing to format_forge_command_name
- Update CommandRegistrar to use format_name callback when available
- Maintains same behavior: Forge commands use hyphenated names, others use dot notation
Benefits:
- Better encapsulation: Forge-specific logic lives in forge integration
- More extensible: Other integrations can provide custom formatters via registrar_config
- Cleaner separation: agents.py doesn't need to know about specific agent requirements
* fix(forge): make format_forge_command_name idempotent
Handle already-hyphenated names (speckit-foo) to prevent double-prefixing
(speckit-speckit-foo). The function now returns already-formatted names
unchanged, making it safe to call multiple times.
Changes:
- Add early return for names starting with 'speckit-'
- Update docstring to clarify accepted input formats
- Add examples showing idempotent behavior
- Add test coverage for idempotent behavior
Examples:
format_forge_command_name('speckit-plan') -> 'speckit-plan' (unchanged)
format_forge_command_name('speckit.plan') -> 'speckit-plan' (converted)
format_forge_command_name('plan') -> 'speckit-plan' (prefixed)
* test(forge): strengthen name field assertions and clarify comments
Improve test_name_field_uses_hyphenated_format to fail loudly when
the name field is missing instead of silently passing.
Changes:
- Add explicit assertion that name_match is not None before validating value
- Ensures test fails if regex doesn't match (e.g., frontmatter rendering changes)
- Clarify Claude comment: it doesn't use inject_name path but SKILL.md
frontmatter still includes hyphenated name via build_skill_frontmatter()
Before: Test would silently pass if 'name:' field was missing from frontmatter
After: Test explicitly asserts field presence before validating format
* docs(forge): clarify frontmatter name requirement and improve test isolation
Fix misleading docstring and improve test to properly validate that the
format_name callback is Forge-specific.
Changes to src/specify_cli/integrations/forge/__init__.py:
- Reword module docstring to clarify the requirement is specifically for
the frontmatter 'name' field value, not command files or invocation
- Before: 'Requires hyphenated command names ... instead of dot notation'
(implied dot notation unsupported overall)
- After: 'Uses a hyphenated frontmatter name value ... for shell compatibility'
(clarifies it's the frontmatter field, and Forge still supports dot filenames)
Changes to tests/integrations/test_integration_forge.py:
- Replace Claude with Windsurf in test_registrar_does_not_affect_other_agents
- Claude uses build_skill_frontmatter() which always includes hyphenated names,
so testing it didn't validate that format_name callback is Forge-only
- Windsurf is a standard markdown agent without inject_name
- Now asserts NO 'name:' field is present, proving format_name isn't invoked
- This properly validates the callback mechanism is isolated to Forge
* test(forge): use parse_frontmatter for precise YAML validation
Replace regex and string searches with CommandRegistrar.parse_frontmatter()
to validate only YAML frontmatter, not entire file content. Prevents false
positives if command body contains 'name:' lines.
Changes:
- test_forge_specific_transformations: Parse frontmatter dict instead of string search
- test_name_field_uses_hyphenated_format: Replace regex with frontmatter parsing
- test_registrar_formats_extension_command_names_for_forge: Use dict validation
- test_registrar_formats_alias_names_for_forge: Use dict validation
Benefits: More precise, robust against body content, better error messages,
consistent with existing codebase utilities.
---------
Co-authored-by: ericnoam <eric.rodriguez@leovegas.com>
|
||
|
|
7f08f31286 |
[stage2] fix: serialize multiline descriptions in legacy TOML renderer (#2097)
* fix: preserve multiline descriptions in legacy toml renderer * refactor: reuse toml escape helper for prompt fallback |
||
|
|
535ddbe0d2 |
fix: add user-invocable: true to skill frontmatter (#2077)
Skills were missing this field, causing them to be treated as "managed" instead of user-invocable via /speckit-* commands. |
||
|
|
b8e7851234 |
feat: add Forgecode agent support (#2034)
* feat: add Forgecode (forge) agent support
- Add 'forgecode' to AGENT_CONFIGS in agents.py with .forge/commands
directory, markdown format, and {{parameters}} argument placeholder
- Add 'forgecode' to AGENT_CONFIG in __init__.py with .forge/ folder,
install URL, and requires_cli=True
- Add forgecode binary check in check_tool() mapping agent key
'forgecode' to the actual 'forge' CLI binary
- Add forgecode case to build_variant() in create-release-packages.sh
generating commands into .forge/commands/ with {{parameters}}
- Add forgecode to ALL_AGENTS in create-release-packages.sh
* fix: strip handoffs frontmatter and replace $ARGUMENTS for forgecode
The forgecode agent hangs when listing commands because the 'handoffs'
frontmatter field (a Claude Code-specific feature) contains 'send: true'
entries that forge tries to act on when indexing .forge/commands/ files.
Additionally, $ARGUMENTS in command bodies was never replaced with
{{parameters}}, so user input was not passed through to commands.
Python path (agents.py):
- Add strip_frontmatter_keys: [handoffs] to the forgecode AGENT_CONFIG
entry so register_commands drops the key before rendering
Bash path (create-release-packages.sh):
- Add extra_strip_key parameter to generate_commands; pass 'handoffs'
for the forgecode case in build_variant
- Use regex prefix match (~ "^"extra_key":") instead of exact
equality to handle trailing whitespace after the YAML key
- Add sed replacement of $ARGUMENTS -> $arg_format in the body
pipeline so {{parameters}} is substituted in forgecode command files
* feat: add name field injection for forgecode agent
Forgecode requires both 'name' and 'description' fields in command
frontmatter. This commit adds automatic injection of the 'name' field
during command generation for forgecode.
Changes:
- Python (agents.py): Add inject_name: True to forgecode config and
implement name injection logic in register_commands
- Bash (create-release-packages.sh): Add post-processing step to inject
name field into frontmatter after command generation
This complements the existing handoffs stripping fix (
|
||
|
|
b1832c9477 |
Stage 6: Complete migration — remove legacy scaffold path (#1924) (#2063)
* Stage 6: Complete migration — remove legacy scaffold path (#1924) Remove the legacy GitHub download and offline scaffold code paths. All 26 agents now use the integration system exclusively. Code removal (~1073 lines from __init__.py): - download_template_from_github(), download_and_extract_template() - scaffold_from_core_pack(), _locate_release_script() - install_ai_skills(), _get_skills_dir (restored slim version for presets) - _has_bundled_skills(), _migrate_legacy_kimi_dotted_skills() - AGENT_SKILLS_MIGRATIONS, _handle_agent_skills_migration() - _parse_rate_limit_headers(), _format_rate_limit_error() - Three-way branch in init() collapsed to integration-only Config derivation (single source of truth): - AGENT_CONFIG derived from INTEGRATION_REGISTRY (replaced 180-line dict) - CommandRegistrar.AGENT_CONFIGS derived from INTEGRATION_REGISTRY (replaced 160-line dict) - Backward-compat constants kept for presets/extensions: SKILL_DESCRIPTIONS, NATIVE_SKILLS_AGENTS, DEFAULT_SKILLS_DIR Release pipeline cleanup: - Deleted create-release-packages.sh/.ps1 (948 lines of ZIP packaging) - Deleted create-github-release.sh, generate-release-notes.sh - Deleted simulate-release.sh, get-next-version.sh, update-version.sh - Removed .github/workflows/scripts/ directory entirely - release.yml is now self-contained: check, notes, release all inlined - Install instructions use uv tool install with version tag Test cleanup: - Deleted test_ai_skills.py (tested removed functions) - Deleted test_core_pack_scaffold.py (tested removed scaffold) - Cleaned test_agent_config_consistency.py (removed 19 release-script tests) - Fixed test_branch_numbering.py (removed dead monkeypatches) - Updated auto-promote tests (verify files created, not tip messages) 1089 tests pass, 0 failures, ruff clean. * fix: resolve merge conflicts with #2051 (claude as skills) - Fix circular import: move CommandRegistrar import in claude integration to inside method bodies (was at module level) - Lazy-populate AGENT_CONFIGS via _ensure_configs() to avoid circular import at class definition time - Set claude registrar_config to .claude/commands (extension/preset target) since the integration handles .claude/skills in setup() - Update tests from #2051 to match: registrar_config assertions, remove --integration tip assertions, remove install_ai_skills mocks 1086 tests pass. * fix: properly preserve claude skills migration from #2051 Restore ClaudeIntegration.registrar_config to .claude/skills (not .claude/commands) so extension/preset registrations write to the correct skills directory. Update tests that simulate claude setup to use .claude/skills and check for SKILL.md layout. Some tests still need updating for the full skills path — 10 remaining failures from the #2051 test expectations around the extension/preset skill registration flow. WIP: 1076/1086 pass. * fix: properly handle SKILL.md paths in extension update rollback and tests Fix extension update rollback using _compute_output_name() for SKILL.md agents (converts dots to hyphens in skill directory names). Previously the backup and cleanup code constructed paths with raw command names (e.g. speckit.test-ext.hello/SKILL.md) instead of the correct computed names (speckit-test-ext-hello/SKILL.md). Test fixes for claude skills migration: - Update claude tests to use .claude/skills paths and SKILL.md layout - Use qwen (not claude) for skills-guard tests since claude's agent dir IS the skills dir — creating it triggers command registration - Fix test_extension_command_registered_when_extension_present to check skills path format 1086 tests pass, 0 failures, ruff clean. * fix: address PR review — lazy init, assertions, deprecated flags - _ensure_configs(): catch ImportError (not Exception), don't set _configs_loaded on failure so retries work - Move _ensure_configs() before unregister loop (not inside it) - Module-level try/except catches ImportError specifically - Remove tautology assertion (or True) in test_extensions.py - Strengthen preset provenance assertion to check source: field - Mark --offline, --skip-tls, --debug, --github-token as hidden deprecated no-ops in init() 1086 tests pass. * fix: remove deleted release scripts from pyproject.toml force-include Removes force-include entries for create-release-packages.sh/.ps1 which were deleted but still referenced in [tool.hatch.build]. |
||
|
|
a858c1d6da |
Install Claude Code as native skills and align preset/integration flows (#2051)
* Use Claude skills for generated commands * Fix Claude integration and preset skill flows * Group Claude tests in integration suite * Align Claude skill frontmatter across generators * Fix native skill preset cleanup * Keep legacy AI skills test on legacy path * Move Claude here-mode test to CLI suite |
||
|
|
4f9d966beb |
Stage 5: Skills, Generic & Option-Driven Integrations (#1924) (#2052)
* Stage 5: Skills, Generic & Option-Driven Integrations (#1924) Add SkillsIntegration base class and migrate codex, kimi, agy, and generic to the integration system. Integrations: - SkillsIntegration(IntegrationBase) in base.py — creates speckit-<name>/SKILL.md layout matching release ZIP output byte-for-byte - CodexIntegration — .agents/skills/, --skills default=True - KimiIntegration — .kimi/skills/, --skills + --migrate-legacy options, dotted→hyphenated skill directory migration - AgyIntegration — .agent/skills/, skills-only (commands deprecated v1.20.5) - GenericIntegration — user-specified --commands-dir, MarkdownIntegration - All four have update-context.sh/.ps1 scripts - All four registered in INTEGRATION_REGISTRY CLI changes: - --ai <agent> auto-promotes to integration path for all registered agents - Interactive agent selection also auto-promotes (bug fix) - --ai-skills and --ai-commands-dir show deprecation notices on integration path - Next-steps display shows correct skill invocation syntax for skills integrations - agy added to CommandRegistrar.AGENT_CONFIGS Tests: - test_integration_base_skills.py — reusable mixin with setup, frontmatter, directory structure, scripts, CLI auto-promote, and complete file inventory (sh+ps) tests - Per-agent test files: test_integration_{codex,kimi,agy,generic}.py - Kimi legacy migration tests, generic --commands-dir validation - Registry updated with Stage 5 keys - Removed 9 dead-mock tests, moved 4 integration tests to proper locations - Fixed all bare project-name tests to use tmp_path - Fixed 6 pre-existing ANSI escape code test failures in test_extensions.py and test_presets.py 1524 tests pass, 0 failures. * fix: remove unused variable flagged by ruff (F841) * fix: address PR review — integration-type-aware deprecation messages and early generic validation - --ai-skills deprecation message now distinguishes SkillsIntegration ("skills are the default") from command-based integrations ("has no effect") - --ai-commands-dir validation for generic runs even when auto-promoted, giving clear CLI error instead of late ValueError from setup() - Resolves review comments from #2052 * fix: address PR review round 2 - Remove unused SKILL_DESCRIPTIONS dict from base.py (dead code after switching to template descriptions for ZIP parity) - Narrow YAML parse catch from Exception to yaml.YAMLError - Remove unused shutil import from test_integration_kimi.py - Remove unused _REGISTRAR_EXEMPT class attr from test_registry.py - Reword --ai-commands-dir deprecation to be actionable - Update generic validation error to mention both --ai and --integration * fix: address PR review round 3 - Clarify parsed_options forwarding is intentional (all options passed, integrations decide what to use) - Extract _strip_ansi() helper in test_extensions.py and test_presets.py - Remove unused pytest import (test_cli.py), unused locals (test_integration_base_skills.py) - Reword --ai-commands-dir deprecation to be actionable without referencing the not-yet-implemented --integration-options * fix: address PR review round 4 - Reorder kimi migration: run super().setup() first so hyphenated targets exist, then migrate dotted dirs (prevents user content loss) - Move _strip_ansi() to shared tests/conftest.py, import from there in test_extensions.py, test_presets.py, test_ai_skills.py - Remove now-unused re imports from all three test files * fix: address PR review round 5 - Use write_bytes() for LF-only newlines (no CRLF on Windows) - Add --integration-options CLI parameter — raw string passed through to the integration via opts['raw_options']; the integration owns parsing of its own options - GenericIntegration.setup() reads --commands-dir from raw_options when not in parsed_options (supports --integration-options="...") - Skip early --ai-commands-dir validation when --integration-options is provided (integration validates in its own setup()) - Remove parse_integration_options from core — integrations parse their own options * fix: address PR review round 6 - GenericIntegration is now stateless: removed self._commands_dir instance state, overrides setup() directly to compute destination from parsed_options/raw_options on the stack - commands_dest() raises by design (stateless singleton) - _quote() in SkillsIntegration now escapes backslashes and double quotes to produce valid YAML even with special characters * fix: address PR review round 7 - Support --commands-dir=value form in raw_options parsing (not just --commands-dir value with space separator) - Normalize CRLF to LF in write_file_and_record() before encoding - Persist ai_skills=True in init-options.json when using a SkillsIntegration, so extensions/presets emit SKILL.md overrides correctly even without explicit --ai-skills flag |
||
|
|
255371d367 |
Stage 3: Standard markdown integrations — 19 agents migrated to plugin architecture (#2038)
* Stage 3: Standard markdown integrations — 19 agents migrated to plugin architecture
Migrate all standard markdown integrations to self-contained subpackages
under integrations/. Each subclasses MarkdownIntegration with config-only
overrides (~10 lines per __init__.py).
Integrations migrated (19):
claude, qwen, opencode, junie, kilocode, auggie, roo, codebuddy,
qodercli, amp, shai, bob, trae, pi, iflow, kiro-cli, windsurf,
vibe, cursor-agent
Changes:
- Create integrations/<key>/ subpackage with __init__.py and scripts/
(update-context.sh, update-context.ps1) for each integration
- Register all 19 in INTEGRATION_REGISTRY (20 total with copilot)
- MarkdownIntegration.setup() processes templates (replaces {SCRIPT},
{ARGS}, __AGENT__; strips frontmatter blocks; rewrites paths)
- Extract install_scripts() to IntegrationBase; refactor copilot to use it
- Generalize --ai auto-promote from copilot-only to registry-driven:
any integration registered in INTEGRATION_REGISTRY auto-promotes.
Unregistered agents (gemini, tabnine, codex, kimi, agy, generic)
continue through the legacy --ai path unchanged.
- Fix cursor/cursor-agent key mismatch in CommandRegistrar.AGENT_CONFIGS
- Add missing vibe entry to CommandRegistrar.AGENT_CONFIGS
- Update kiro alias test to reflect auto-promote behavior
Testing:
- Per-agent test files (test_integration_<agent>.py) with shared mixin
- 1316 tests passing, 0 failures
- Complete file inventory tests for both sh and ps variants
- Byte-for-byte validated against v0.4.3 release packages (684 files)
* Address PR review: fix repo root detection and no-op test
- Fix repo root fallback in all 20 update-context.sh scripts: walk up
from script location to find .specify/ instead of falling back to pwd
- Fix repo root fallback in all 20 update-context.ps1 scripts: walk up
from script location to find .specify/ instead of falling back to $PWD
- Add assertions to test_setup_writes_to_correct_directory: verify
expected_dir exists and all command files reside under it
* Fix REPO_ROOT priority: prefer .specify walk-up over git root
In monorepos the git toplevel may differ from the project root that
contains .specify/. The previous fix still preferred git rev-parse
over the walk-up result.
Bash scripts (20): prefer the discovered _root when it contains
.specify/; only accept git root if it also contains .specify/.
PowerShell scripts (20): validate git root contains .specify/ before
using it; fall back to walking up from script directory otherwise.
* Guard git call with try/catch in PowerShell scripts
With $ErrorActionPreference = 'Stop', an unguarded git rev-parse
throws a terminating CommandNotFoundException when git is not
installed, preventing the .specify walk-up fallback from running.
Wrap the git call in try/catch across all 20 update-context.ps1
scripts so the fallback works reliably without git.
* Rename hyphenated package dirs to valid Python identifiers
Rename kiro-cli → kiro_cli and cursor-agent → cursor_agent so the
packages can be imported with normal Python syntax instead of
importlib. The user-facing integration key (IntegrationBase.key)
stays hyphenated to match the actual CLI tool / binary name.
Also reorganize _register_builtins(): imports and registrations
are now grouped alphabetically with clear section comments.
* Reuse CommandRegistrar path rewriting in process_template()
Replace the duplicated regex-based path rewriting in
MarkdownIntegration.process_template() with a call to the shared
CommandRegistrar._rewrite_project_relative_paths() implementation.
This ensures extension-local paths are preserved and boundary rules
stay consistent across the codebase.
* Promote _rewrite_project_relative_paths to public API
Rename CommandRegistrar._rewrite_project_relative_paths() to
rewrite_project_relative_paths() (drop leading underscore) so
integrations can call it without reaching into a private method
across subsystem boundaries.
Addresses PR review feedback:
https://github.com/github/spec-kit/pull/2038#discussion_r3022105627
* Broaden TestRegistrarKeyAlignment to cover all integration keys
Parametrize across ALL_INTEGRATION_KEYS instead of only checking
cursor-agent and vibe. Keeps a separate negative test for the
stale 'cursor' shorthand.
Addresses PR review feedback:
https://github.com/github/spec-kit/pull/2038#discussion_r3022269032
|
||
|
|
ccc44dd00a |
Unify Kimi/Codex skill naming and migrate legacy dotted Kimi dirs (#1971)
* fix: unify hyphenated skills and migrate legacy kimi dotted dirs * fix: preserve legacy kimi dotted preset skill overrides * fix: migrate kimi legacy dotted skills without ai-skills flag * fix: harden kimi migration and cache hook init options * fix: apply kimi preset skill overrides without ai-skills flag * fix: keep sequential branch numbering beyond 999 * test: align kimi scaffold skill path with hyphen naming * chore: align hook typing and preset skill comment * fix: restore AGENT_SKILLS_DIR_OVERRIDES compatibility export * refactor: remove AGENT_SKILLS_DIR_OVERRIDES and update callers * fix(ps1): support sequential branch numbers above 999 * fix: resolve preset skill placeholders for skills agents * Fix legacy kimi migration safety and preset skill dir checks * Harden TOML rendering and consolidate preset skill restore parsing * Fix PowerShell overflow and hook message fallback for empty invocations * Restore preset skills from extensions * Refine preset skill restore helpers * Harden skill path and preset checks * Guard non-dict init options * Avoid deleting unmanaged preset skill dirs * Unify extension skill naming with hooks * Harden extension native skill registration * Normalize preset skill titles |
||
|
|
a351c826ee |
fix(cli): add allow_unicode=True and encoding="utf-8" to YAML I/O (#1936)
None of the yaml.dump() calls specify allow_unicode=True, causing non-ASCII characters in extension descriptions to be escaped to \uXXXX sequences in generated .agent.md frontmatter and config files. Add allow_unicode=True to all 6 yaml.dump() call sites, and encoding="utf-8" to all corresponding write_text() and read_text() calls to ensure consistent UTF-8 handling across platforms. |
||
|
|
191f33213c |
fix: Align native skills frontmatter with install_ai_skills (#1920)
* docs(sdk): align native skills frontmatter + document multi-generator drift * fix: clarify skills frontmatter contract and AGENTS sections |
||
|
|
34171efcef |
Add support for Junie (#1831)
* Add support for Junie * Add Junie agent configuration to specify-cli * Add support for iflow agent in context update scripts |
||
|
|
c8af730b14 |
feat: migrate Codex/agy init to native skills workflow (#1906)
* feat: migrate codex and agy to native skills flow * fix: harden codex skill frontmatter and script fallback * fix: clarify skills separator default expansion * fix: rewrite agent_scripts paths for codex skills * fix: align kimi guidance and platform-aware codex fallback |
||
|
|
f6794685b6 |
feat: add iFlow CLI support (#1875)
Add `iflow` as a supported AI agent (the key users pass to --ai) across all relevant configuration files, release scripts, agent context scripts, and README. Includes consistency tests following the same pattern as kimi/tabnine additions. - README: describe `check` generically (git + all AGENT_CONFIG CLI agents) - README: describe `--ai` with reference to AGENT_CONFIG for full list Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
1a21bdef01 |
Feature/spec kit add pi coding agent pullrequest (#1853)
* feat(ai): add native support for Pi coding agent by pi+gpt 5.4 * docs(pi): document MCP limitations for Pi agent * fix: unitended kimi agent mention added to update-agent-context.ps1 * fix: address reviewer feedback * Apply suggestions from code review Changes in AGENTS.md weren't part of my PR, but the Copilot feedback seems to be correct is correct. I've doublechecked it with contents of test_agent_config_consistency.py and create-release-packages scripts 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> |
||
|
|
a177a1a6d1 |
feat: add Trae IDE support as a new agent (#1817)
* feat: add Trae IDE support as a new agent Add Trae (https://www.trae.ai/) as a supported AI agent in spec-kit. Trae is an IDE-based agent that uses .trae/rules/ directory for project-level rules in Markdown format. Changes across 9 files: - src/specify_cli/__init__.py: Add trae to AGENT_CONFIG (IDE-based, .trae/ folder, rules subdir, no CLI required) - src/specify_cli/extensions.py: Add trae to CommandRegistrar.AGENT_CONFIGS (.trae/rules, markdown format, .md extension) - README.md: Add Trae to supported agents table, CLI examples, and --ai option description - .github/workflows/scripts/create-release-packages.sh: Add trae to ALL_AGENTS array and build case statement - .github/workflows/scripts/create-release-packages.ps1: Add trae to AllAgents array and switch statement - .github/workflows/scripts/create-github-release.sh: Add trae template zip files to release assets - scripts/bash/update-agent-context.sh: Add TRAE_FILE, trae case in update function, and auto-detect block - scripts/powershell/update-agent-context.ps1: Add TRAE_FILE, ValidateSet entry, switch case, and auto-detect block - tests/test_agent_config_consistency.py: Add 8 consistency tests for trae following established kimi/tabnine patterns * fix: correct Generate-Commands parameter names for trae in PowerShell release script Fix incorrect parameter names in the trae case of Build-Variant: - -Format -> -Extension - -ArgsToken -> -ArgFormat - -OutDir -> -OutputDir These now match the Generate-Commands function signature and all other agent entries in the script. Co-authored-by: Copilot <copilot@github.com> * Update release packaging scripts and agent docs * Update Agent.md * Restore format * Adjust order * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Unused * fix: add TRAE_FILE to update_all_existing_agents() for auto-detect support Add missing update_if_new call for TRAE_FILE in the bash update-agent-context.sh script's update_all_existing_agents() function, matching the PowerShell implementation. This ensures running the script without arguments will correctly auto-detect and update existing Trae agent files. * Add configuration for 'trae' in agents.py * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Refactor trae configuration test for clarity * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Update update-agent-context.sh * Fix formatting in update-agent-context.sh --------- Co-authored-by: root <root@g340-cd52-700-60f9-5561-211-6c32.byted.org> Co-authored-by: root <root@g340-cd52-700-c3d1-c735-796-4b9e.byted.org> Co-authored-by: Copilot <copilot@github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> |
||
|
|
69ee7a836e |
feat(presets): Pluggable preset system with catalog, resolver, and skills propagation (#1787)
* Initial plan * feat(templates): add pluggable template system with packs, catalog, resolver, and CLI commands Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com> * test(templates): add comprehensive unit tests for template pack system Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com> * feat(presets): pluggable preset system with template/command overrides, catalog, and resolver - Rename 'template packs' to 'presets' to avoid naming collision with core templates - PresetManifest, PresetRegistry, PresetManager, PresetCatalog, PresetResolver in presets.py - Extract CommandRegistrar to agents.py as shared infrastructure - CLI: specify preset list/add/remove/search/resolve/info - CLI: specify preset catalog list/add/remove - --preset option on specify init - Priority-based preset stacking (--priority, lower = higher precedence) - Command overrides registered into all detected agent directories (17+ agents) - Extension command safety: skip registration if target extension not installed - Multi-catalog support: env var, project config, user config, built-in defaults - resolve_template() / Resolve-Template in bash/PowerShell scripts - Self-test preset: overrides all 6 core templates + 1 command - Scaffold with 4 examples: core/extension template and command overrides - Preset catalog (catalog.json, catalog.community.json) - Documentation: README.md, ARCHITECTURE.md, PUBLISHING.md - 110 preset tests, 253 total tests passing * feat(presets): propagate command overrides to skills via init-options - Add save_init_options() / load_init_options() helpers that persist CLI flags from 'specify init' to .specify/init-options.json - PresetManager._register_skills() overwrites SKILL.md files when --ai-skills was used during init and corresponding skill dirs exist - PresetManager._unregister_skills() restores core template content on preset removal - registered_skills stored in preset registry metadata - 8 new tests covering skill override, skip conditions, and restore * fix: address PR check failures (ruff F541, CodeQL URL substring) - Remove extraneous f-prefix from two f-strings without placeholders - Replace substring URL check in test with startswith/endswith assertions to satisfy CodeQL incomplete URL substring sanitization rule * fix: address Copilot PR review comments - Move save_init_options() before preset install so skills propagation works during 'specify init --preset --ai-skills' - Clean up downloaded ZIP after successful preset install during init - Validate --from URL scheme (require HTTPS, HTTP only for localhost) - Expose unregister_commands() on extensions.py CommandRegistrar wrapper instead of reaching into private _registrar field - Use _get_merged_packs() for search() and get_pack_info() so all active catalogs are searched, not just the highest-priority one - Fix fetch_catalog() cache to verify cached URL matches current URL - Fix PresetResolver: script resolution uses .sh extension, consistent file extensions throughout resolve(), and resolve_with_source() delegates to resolve() to honor template_type parameter - Fix bash common.sh: fall through to directory scan when python3 returns empty preset list - Fix PowerShell Resolve-Template: filter out dot-folders and sort extensions deterministically * fix: narrow empty except blocks and add explanatory comments * fix: address Copilot PR review comments (round 2) - Fix init --preset error masking: distinguish "not found" from real errors - Fix bash resolve_template: skip hidden dirs in extensions (match Python/PS) - Fix temp dir leaks in tests: use temp_dir fixture instead of mkdtemp - Fix self-test catalog entry: add note that it's local-only (no download_url) - Fix Windows path issue in resolve_with_source: use Path.relative_to() - Fix skill restore path: use project's .specify/templates/commands/ not source tree - Add encoding="utf-8" to all file read/write in agents.py - Update test to set up core command templates for skill restoration * fix: remove self-test from catalog.json (local-only preset) * fix: address Copilot PR review comments (round 3) - Fix PS Resolve-Template fallback to skip dot-prefixed dirs (.cache) - Rename _catalog to _catalog_name for consistency with extension system - Enforce install_allowed policy in CLI preset add and download_pack() - Fix shell injection: pass registry path via env var instead of string interpolation * fix: correct PresetError docstring from template to preset * Removed CHANGELOG requirement * Applying review recommendations * Applying review recommendations * Applying review recommendations * Applying review recommendations --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com> |