* fix: resolve command references per integration type (dot vs hyphen)
Replace hardcoded /speckit.<cmd> references in templates with
__SPECKIT_COMMAND_<NAME>__ placeholders that are resolved at
setup time based on the integration type:
- Markdown/TOML/YAML agents: separator='.' → /speckit.plan
- Skills agents: separator='-' → /speckit-plan
Changes:
- Add resolve_command_refs() static method to IntegrationBase
- Add invoke_separator class attribute (. for base, - for skills)
- Wire into process_template() as step 8
- Update _install_shared_infra() to process page templates
- Replace /speckit.* in 5 command templates and 3 page templates
- Add unit tests for resolve_command_refs (positive + negative)
- Add integration tests verifying on-disk content for all agents
- Add end-to-end CLI tests for Claude (skills) and Copilot (markdown)
Fixes#2347
* review: use effective_invoke_separator() for Copilot skills mode
Address PR review feedback: instead of bleeding _skills_mode
knowledge into the CLI layer, add effective_invoke_separator()
method to IntegrationBase that accepts parsed_options.
CopilotIntegration overrides it to return "-" when skills
mode is requested. The CLI layer simply asks the integration
for its separator — no hasattr or _skills_mode coupling.
Also adds tests for the new method on both base and Copilot,
plus an end-to-end test for 'specify init --integration copilot
--integration-options --skills' verifying page templates get
hyphen refs.
* fix: build_command_invocation preserves full suffix for extension commands
Previously rsplit('.', 1)[-1] on 'speckit.git.commit' yielded
just 'commit', producing /speckit.commit instead of
/speckit.git.commit (or /speckit-git-commit for skills).
Fix: strip only the 'speckit.' prefix when present, then join
remaining segments with the appropriate separator.
Updated in IntegrationBase, SkillsIntegration, and
CopilotIntegration. Added tests for extension commands in
build_command_invocation across all three.
* fix: Copilot dispatch_command() preserves full extension command suffix
dispatch_command() had the same rsplit('.', 1)[-1] bug as
build_command_invocation() — speckit.git.commit would dispatch
as /speckit-commit instead of /speckit-git-commit in skills
mode, or --agent speckit.commit instead of speckit.git.commit
in default mode.
* feat: Git extension stage 2 — GIT_BRANCH_NAME override, --force for existing dirs, auto-install tests (#1940)
- Add GIT_BRANCH_NAME env var override to create-new-feature.sh/.ps1
for exact branch naming (bypasses all prefix/suffix generation)
- Fix --force flag for 'specify init <dir>' into existing directories
- Add TestGitExtensionAutoInstall tests (auto-install, --no-git skip,
commands registered)
- Add TestFeatureDirectoryResolution tests (env var, feature.json,
priority, branch fallback)
- Document GIT_BRANCH_NAME in speckit.git.feature.md and specify.md
* fix: remove unused Tuple import (ruff F401)
* fix: address Copilot review feedback (#2117)
- Fix timestamp regex ordering: check YYYYMMDD-HHMMSS before generic
numeric prefix in both bash and PowerShell
- Set BRANCH_SUFFIX in GIT_BRANCH_NAME override path so 244-byte
truncation logic works correctly
- Add 244-byte length check for GIT_BRANCH_NAME in PowerShell
- Use existing_items for non-empty dir warning with --force
- Skip git extension install if already installed (idempotent --force)
- Wrap PowerShell feature.json parsing in try/catch for malformed JSON
- Fix PS comment: 'prefix lookup' -> 'exact mapping via Get-FeatureDir'
- Remove non-functional SPECIFY_SPEC_DIRECTORY from specify.md template
* fix: address second round of Copilot review feedback (#2117)
- Guard shutil.rmtree on init failure: skip cleanup when --force merged
into a pre-existing directory (prevents data loss)
- Bash: error on GIT_BRANCH_NAME >244 bytes instead of broken truncation
- Fix malformed numbered list in specify.md (restore missing step 1)
- Add claude_skills.exists() assert before iterdir() in test
* fix: use UTF-8 byte count for 244-byte branch name limit (#2117)
- Bash: use LC_ALL=C wc -c for byte length instead of ${#VAR}
- PowerShell: use [System.Text.Encoding]::UTF8.GetByteCount() instead
of .Length (UTF-16 code units)
* fix: address third round of review feedback (#2117)
- Update --dry-run help text in bash and PowerShell (branch name only)
- Fix specify.md JSON example: use concrete path, not literal variable
- Add TestForceExistingDirectory tests (merge + error without --force)
- Add PowerShell Get-FeaturePathsEnv tests (env var + feature.json)
* fix: normalize relative paths and fix Test-HasGit compat (#2117)
- Bash common.sh: normalize SPECIFY_FEATURE_DIRECTORY and feature.json
relative paths to absolute under repo root
- PowerShell common.ps1: same normalization using IsPathRooted + Join-Path
- PowerShell create-new-feature.ps1: call Test-HasGit without -RepoRoot
for compatibility with core common.ps1 (no param) and git-common.ps1
(optional param with default)
* test: add GIT_BRANCH_NAME automated tests for bash and PowerShell (#2117)
- TestGitBranchNameOverrideBash: 5 tests (exact name, sequential prefix,
timestamp prefix, overlong rejection, dry-run)
- TestGitBranchNameOverridePowerShell: 4 tests (exact name, sequential
prefix, timestamp prefix, overlong rejection)
- Tests use extension scripts (not core) via new ext_git_repo and
ext_ps_git_repo fixtures
* fix: restore git init during specify init + review fixes (#2117)
- Restore is_git_repo() and init_git_repo() functions removed in stage 2
- specify init now runs git init AND installs git extension (not just
extension install alone)
- Add is_dir() guard for non-here path to prevent uncontrolled error
when target exists but is a file
- Add python3 JSON fallback in common.sh for multi-line feature.json
(grep pipeline fails on pretty-printed JSON without jq)
* fix: use init_git_repo error_msg in failure output (#2117)
* fix: ensure_executable_scripts also covers .specify/extensions/ (#2117)
Extension .sh scripts (e.g. create-new-feature.sh, initialize-repo.sh)
may lack execute bits after install. Scan both .specify/scripts/ and
.specify/extensions/ for permission fixing.
* fix: move chmod after extension install + sanitize error_msg (#2117)
- ensure_executable_scripts() now runs after git extension install so
extension .sh files get execute bits in the same init run
- Sanitize init_git_repo error_msg to single line (replace newlines,
truncate to 120 chars) to prevent garbled StepTracker output
* fix: use tracker.error for git init/extension failures (#2117)
Git init failure and extension install failure were reported as
tracker.complete (showing green) even on error. Now track a
git_has_error flag and call tracker.error when any step fails,
so the UI correctly reflects the failure state.
* fix: sanitize ext_err in git step tracker for consistent rendering (#2117)
* feat(commands): wire before/after hook events into specify and plan templates
Replicates the hook evaluation pattern from tasks.md and implement.md
(introduced in PR #1702) into the specify and plan command templates.
This completes the hook lifecycle across all SDD phases.
Changes:
- specify.md: Add before_specify/after_specify hook blocks
- plan.md: Add before_plan/after_plan hook blocks
- EXTENSION-API-REFERENCE.md: Document new hook events
- EXTENSION-USER-GUIDE.md: List all available hook events
Fixes#1788
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Mark before_commit/after_commit as planned in extension docs
These hook events are defined in the API reference but not yet wired
into any core command template. Marking them as planned rather than
removing them, since the infrastructure supports them.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix hook enablement to default true when field is absent
Matches HookExecutor.get_hooks_for_event() semantics where
hooks without an explicit enabled field are treated as enabled.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(docs): mark commit hooks as planned in user guide config example
The yaml config comment listed before_commit/after_commit as
"Available events" but they are not yet wired into core templates.
Moved them to a separate "Planned" line, consistent with the
API reference.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(commands): align enabled-filtering semantics across all hook templates
tasks.md and implement.md previously said "Filter to only hooks where
enabled: true", which would skip hooks that omit the enabled field.
Updated to match specify.md/plan.md and HookExecutor's h.get('enabled', True)
behavior: filter out only hooks where enabled is explicitly false.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: remove per-short-name number detection from specify prompt
The specify.md prompt instructed the AI to search for existing branches
filtered by the exact short-name, causing every new feature to start at
001 since no branches matched the new short-name. The underlying
create-new-feature.sh script already has correct global numbering logic
via check_existing_branches() that searches ALL branches and spec
directories.
The fix removes the AI's flawed number-detection steps and tells it to
NOT pass --number, letting the script auto-detect the next globally
available number.
Closes#1744Closes#1468🤖 Generated with [Claude Code](https://claude.com/code)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: clarify --json flag requirement per Copilot review
- Rephrased step 2 to mention both --short-name and --json flags
- Added explicit note to always include the JSON flag for reliable output parsing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- Use git ls-remote for more reliable remote branch detection
- Check remote branches, local branches, AND specs directories
- Match exact short-name pattern to avoid false positives
- Ensures no duplicate numbers across all sources
- Add --number parameter to create-new-feature scripts (bash & PowerShell)
- Add check_existing_branches() function to fetch and scan remote branches
- Update branch numbering logic to check remotes before creating new branches
- Update /speckit.specify command to document remote branch checking workflow
- Prevents duplicate branch numbers when branches exist on remotes but not locally
- Maintains backward compatibility with existing workflows
- Falls back to local directory scanning when Git is not available
Add automated markdown linting to ensure consistent formatting across
all markdown files in the repository.
Changes:
- Add .markdownlint-cli2.jsonc configuration
- Create .github/workflows/lint.yml for CI/CD integration
- Fix all 908 existing markdown errors across 27 files
- Enforce ATX-style headings and asterisk emphasis
- Set consistent 2-space list indentation
This prevents markdown errors after project initialization and
maintains high documentation quality standards.