Commit Graph

15 Commits

Author SHA1 Message Date
Manfred Riem
4038d370bf chore: align CI Python matrix with devguide lifecycle + fix bash 3.2 portability (#3244)
* chore: align CI Python matrix with devguide release lifecycle

Run the pytest matrix only on the bugfix (maintenance) releases — 3.13
and 3.14 — instead of 3.11/3.12/3.13, and point the ruff lint job at the
latest interpreter (3.14). The supported floor stays at requires-python
>= 3.11 (oldest non-EOL security release): older security versions are
supported by claim and fixed reactively rather than gated on a wide
per-commit matrix. Also add macos-latest to the OS matrix so macOS
regressions are caught.

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: make bash scripts portable to bash 3.2 (macOS system /bin/bash)

Adding macos-latest to the CI matrix surfaced two pre-existing bash 3.2
incompatibilities (macOS ships bash 3.2 as /bin/bash):

1. update-agent-context.sh embedded Python heredocs inside $(...) command
   substitution. bash 3.2 mis-parses an apostrophe in a heredoc body
   nested in $(...), failing with "unexpected EOF while looking for
   matching `''". Removed the apostrophes from the affected $()-nested
   heredoc body and documented the constraint to prevent regressions.

2. create-new-feature-branch.sh and create-new-feature.sh used the
   bash 4+ ${word^^} uppercase parameter expansion, which errors as a
   "bad substitution" on bash 3.2 and caused short uppercase acronyms
   (e.g. "GO") to be dropped from derived branch names. Replaced with a
   portable `tr '[:lower:]' '[:upper:]'` pipeline.

Verified the full test suite passes under bash 3.2.57 and shellcheck
(--severity=error) is clean.

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: address review feedback on bash 3.2 portability changes

- create-new-feature.sh: replace the non-portable `\b...\b` grep
  word-boundary (BSD grep treats `\b` as a backspace, so the acronym
  branch could silently fail) with `grep -qw`, matching its twin
  create-new-feature-branch.sh, and pipe the description via
  `printf '%s'` instead of `echo`.
- create-new-feature-branch.sh: switch the acronym check to
  `printf '%s'` as well so both twins are identical and avoid `echo`
  on user-provided text.
- update-agent-context.sh: reword the apostrophe-free self-seeding
  comment to be clearer and less easy to misread.

Verified under bash 3.2.57 (full bash-script suite green) and
shellcheck --severity=error.

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-30 06:43:48 -05:00
Ali jawwad
4badf3b5b1 fix(scripts): use ASCII [OK] marker in initialize-repo.sh (parity with PowerShell twin) (#3231)
* fix(scripts): use ASCII [OK] marker in initialize-repo.sh (parity with PowerShell twin)

initialize-repo.sh printed its success line with a Unicode checkmark ('✓ Git repository initialized'), while the PowerShell twin initialize-repo.ps1 and both auto-commit scripts use the ASCII marker '[OK]'. That is an output-text divergence across the bash/PowerShell twins and an inconsistency among sibling extension scripts. Use '[OK]' to match.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test: assert full [OK] init line and surface stderr on failure

Address Copilot review: assert the full success line '[OK] Git repository initialized' (not just the '[OK]' substring, which could pass if unrelated [OK] output is added later) and include result.stderr in the assertion message so a failure is debuggable.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 16:56:06 -05:00
Ali jawwad
7b687d8bbd fix(scripts): drop HAS_GIT from PowerShell git-extension output (parity with bash) (#3195)
* fix(scripts): drop HAS_GIT from PowerShell git-extension output (parity with bash)

create-new-feature-branch.ps1 emitted a HAS_GIT key in its JSON output and a 'HAS_GIT:' line in text output that the bash twin never emits. The bash output contract is {BRANCH_NAME, FEATURE_NUM} (+ DRY_RUN) only, so a tool parsing the machine-readable output got a different shape on Windows/PowerShell vs macOS/Linux -- a cross-platform contract divergence.

$hasGit is still computed and used internally for branch-creation logic; only its two output emissions are removed, restoring parity. Added regression tests asserting neither the PS nor the bash output contains HAS_GIT (JSON and text). Noted as a follow-up in #3129.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: note DRY_RUN in the HAS_GIT-omission comment (parity)

Address Copilot review: the comment described the output contract as {BRANCH_NAME, FEATURE_NUM} without mentioning that DRY_RUN is still conditionally added in JSON mode on dry runs. Clarify so the contract description is complete for future maintainers. Comment-only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 14:36:17 -05:00
Ali jawwad
9fe1c4cc5c fix(scripts): keep PowerShell branch-name acronym match case-sensitive (parity with bash) (#3129)
* fix(scripts): keep PowerShell branch-name acronym match case-sensitive

Get-BranchName keeps a sub-3-character word only when it appears as an
UPPERCASE acronym in the description. The bash twin checks this
case-sensitively (grep "\b${word^^}\b" / grep -qw -- "${word^^}"), but the
PowerShell twin used -match, which is case-INSENSITIVE, so it kept EVERY
short word regardless of case -- contradicting its own comment and diverging
from bash. The same description then produced different spec-directory and
branch names on Windows/PowerShell vs macOS/Linux (e.g. "Add go support" ->
001-go-support instead of 001-support), desyncing specs/, feature.json, and
git branches across a mixed-OS team.

Use the case-sensitive -cmatch so a short word is kept only for a genuine
uppercase acronym, matching bash. Applied to both the core
scripts/powershell/create-new-feature.ps1 and the git extension's
create-new-feature-branch.ps1.

Add bash + PowerShell regression tests (core and git-extension) asserting a
lowercase short word is dropped while an uppercase acronym is kept.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test: fix article grammar in branch-name docstrings

Address review: 'an UPPERCASE acronym' -> 'an acronym in UPPERCASE' across the four branch-name case-sensitivity test docstrings (the indefinite article reads cleanly before 'acronym'). Docstring-only; no behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 07:56:59 -05:00
Pascal THUET
a17a658bbd feat(scripts): add SPECIFY_INIT_DIR to target a member project from the repo root (#2892)
* feat(scripts): add SPECIFY_INIT_DIR to target a member project from the repo root

Resolve an explicit SPECIFY_INIT_DIR project override once in the core
get_repo_root / Get-RepoRoot, so a non-interactive / CI caller can target a
member project (the directory containing .specify/) from a monorepo root
without cd. Strict by design: the path must exist and contain .specify/,
otherwise it hard-errors with no silent fallback.

- Single resolver in core; the git feature-branch script inherits it by
  sourcing core, with no per-extension copies.
- PS resolver verifies the resolved path is a directory (Resolve-Path also
  succeeds for files) so a file value errors as "not an existing directory".
- get_feature_paths splits decl/assignment so a SPECIFY_INIT_DIR failure
  propagates instead of being masked by `local`.
- create-new-feature-branch: when core is absent (only git-common loaded) and
  SPECIFY_INIT_DIR is set, hard-error rather than silently using the git root.
- Document SPECIFY_INIT_DIR and SPECIFY_FEATURE_DIRECTORY in the core reference.
- Tests for valid/relative/trailing-slash/file/missing/no-.specify targets,
  feature-axis composition, the no-core guard, and a PowerShell mirror.

* fix: guard SPECIFY_INIT_DIR with stale core scripts

* docs: clarify SPECIFY_FEATURE_DIRECTORY precedence wording

* fix: normalize trailing slash in PowerShell SPECIFY_INIT_DIR resolver

Resolve-Path preserves a trailing separator from its input, so a
SPECIFY_INIT_DIR ending in a slash returned a root that didn't match the
bash resolver (whose `cd && pwd` strips it). That broke
test_ps_trailing_slash_tolerated on the CI runners, which do have pwsh.
Trim it with TrimEndingDirectorySeparator (no-op on a bare root or a path
with no trailing separator).

Also fix the misleading test comment: the PowerShell mirror runs on the
CI ubuntu/windows runners (they ship pwsh), it is not skipped there.

* test: normalize bash path expectations on Windows

* docs: clarify SPECIFY_INIT_DIR root helpers
2026-06-19 12:05:42 -05:00
LuoHui1
4eda983950 fix: count worktree branches in git extension numbering (#3054)
* fix: count worktree branches in git extension numbering

* fix: preserve literal plus branch prefixes
2026-06-18 09:40:32 -05:00
Copilot
927f54feea feat: make git extension opt-in and remove --no-git at v0.10.0 (#2873)
* feat(init)!: make git extension opt-in and remove --no-git at v0.10.0

- Remove --no-git parameter from specify init command
- Remove git extension auto-installation from init flow
- Git repository initialization (git init) still runs when git is available
- Remove --no-git from all test invocations across the test suite
- Update docs to reflect opt-in git extension behavior
- Replace TestGitExtensionAutoInstall with TestGitExtensionOptIn tests

BREAKING CHANGE: specify init no longer auto-installs the git extension.
Use `specify extension add git` to install it explicitly.
The --no-git flag has been removed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* refactor(scripts): remove git operations from core scripts

Git functionality is now entirely managed by the git extension.
Core scripts only handle directory-based feature creation and numbering.

- Remove has_git(), check_feature_branch(), git branch creation from core
- Simplify number detection to use only spec directory scanning
- Remove HAS_GIT output from get_feature_paths()
- Remove git remote fetching and branch querying
- Keep BRANCH_NAME output key for backward compatibility

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* refactor: remove all git operations from core

- Remove is_git_repo() and init_git_repo() dead code from _utils.py
- Remove --branch-numbering from init command
- Remove git from 'specify check' (now extension-only)
- Update docs: git is optional prerequisite, check command description
- Fix tests to reflect no-git-in-core reality (fallback to main)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* refactor(scripts): remove directory scanning and branch fallback from core

Core scripts now resolve feature context exclusively from:
1. SPECIFY_FEATURE env var (set by git extension)
2. .specify/feature.json (persisted by specify command)

Removed find_feature_dir_by_prefix() and directory scanning heuristics —
these are the git extension's responsibility. Scripts error clearly when
no feature context is available.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: introduce feature_numbering, deprecate branch_numbering in init-options

- specify command template now reads feature_numbering (preferred) with
  fallback to branch_numbering (deprecated) from init-options.json
- Git extension reads git-config.yml > feature_numbering > branch_numbering
- init now writes feature_numbering: sequential to init-options.json
- Deprecation warning emitted when branch_numbering is used as fallback

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: remove trailing whitespace in common.ps1

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat(scripts): persist SPECIFY_FEATURE_DIRECTORY env var to feature.json

When SPECIFY_FEATURE_DIRECTORY is set, get_feature_paths() now writes the
value to .specify/feature.json so future sessions without the env var can
still resolve the feature directory. The write is idempotent — it skips
when the file already contains the same value.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: address review feedback — error messages and docs

- Update error messages in common.sh and common.ps1 to reference
  SPECIFY_FEATURE_DIRECTORY instead of SPECIFY_FEATURE (which no longer
  resolves feature directories)
- Fix get_current_branch comment (returns empty string, not error)
- Update upgrade.md to reference SPECIFY_FEATURE_DIRECTORY with correct
  example paths
- Update local-development.md troubleshooting: replace stale 'Git step
  skipped' row with actionable git extension guidance

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(scripts): harden feature.json persistence

- Use json_escape in printf fallback when jq is unavailable (common.sh)
- Replace utf8NoBOM encoding with UTF8Encoding($false) for PowerShell
  5.1 compatibility (common.ps1)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* refactor(scripts): remove dead feature_json_matches_feature_dir functions

These guards are no longer needed since the branch-name validation they
protected against has been removed from check-prerequisites.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* refactor(git-ext): rename create-new-feature to create-new-feature-branch

The git extension's script only creates the git branch — rename it to
reflect that responsibility. The core create-new-feature.sh/.ps1 handles
feature directory creation and feature.json persistence.

Also includes fixes from review feedback:
- common.sh: _persist_feature_json uses json_escape fallback
- common.ps1: Save-FeatureJson uses UTF8Encoding for PS 5.1 compat
- common.ps1: case-sensitive path stripping on non-Windows
- create-new-feature.sh/ps1: output both SPECIFY_FEATURE and
  SPECIFY_FEATURE_DIRECTORY
- setup-tasks.sh: fix stale 'Validate branch' comment

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(tests): update references to renamed git extension scripts

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(tests): remove duplicate EXT_CREATE_FEATURE assignments

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Manfred Riem <mnriem@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-09 06:13:07 -05:00
Copilot
c7e0cacaff fix: PS 5.1 compat — replace non-ASCII chars in shipped PowerShell scripts (#2709)
* Initial plan

* fix: replace non-ASCII chars in PS1 files, add encoding regression tests, fix ANSI stripping in tests, update docs

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-05-26 13:18:34 -05:00
Manfred Riem
13d88d22a6 fix: replace xargs trim with sed to handle quotes in descriptions (#2351)
xargs re-parses stdin as shell tokens, causing 'unterminated quote'
errors when feature descriptions contain apostrophes, double quotes,
or backslashes. Replace with sed-based whitespace trim that preserves
input verbatim.

Add regression tests for special characters in descriptions (core and
extension scripts), plus a negative test for whitespace-only input.

Fixes #2339
2026-04-24 07:13:36 -05:00
saram ali
ba9a8b8e59 fix: suppress CRLF warnings in auto-commit.ps1 (#2258)
* fix: suppress CRLF warnings in auto-commit.ps1 (#2253)

Replace 2> with 2>&1 redirection and  assignment to properly
suppress stderr output including CRLF warnings on Windows. Exit code
logic preserved for change detection.

Fixes #2253

* fix: use SilentlyContinue for CRLF stderr handling, add tests

The 2>&1 approach still raises terminating errors under
$ErrorActionPreference='Stop'.  Instead, temporarily set
SilentlyContinue around all native git calls that may emit
CRLF warnings to stderr (rev-parse, diff, ls-files, add, commit).

Adds 5 pytest tests (TestAutoCommitPowerShellCRLF) that set
core.autocrlf=true with LF-ending files.  On Windows runners
this triggers actual CRLF warnings; on other platforms the tests
pass trivially.

Fixes #2253

* refactor: address Copilot review feedback

- Use 'Continue' instead of 'SilentlyContinue' so error output is
  still captured in $out for diagnostics on real git failures.
- Wrap all three EAP save/restore blocks in try/finally to guarantee
  restoration even on unexpected exceptions.
- Fix CRLF test to commit a tracked LF file first, then modify it,
  so git diff --quiet HEAD actually inspects the tracked change and
  triggers the CRLF warning on Windows.

* test: assert CRLF warning fires on Windows

On Windows, probe git diff stderr before running the script to verify
the test setup actually produces the expected CRLF warning.  This
makes the regression test deterministic on the Windows runner.  On
non-Windows the probe is skipped (warnings don't fire there).

---------

Co-authored-by: Manfred Riem <15701806+mnriem@users.noreply.github.com>
2026-04-17 09:27:45 -05:00
Manfred Riem
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.
2026-04-15 14:35:05 -05:00
Gabriel Henrique
4687c33b0f feat(scripts): optional single-segment branch prefix for gitflow (#2202)
* feat(scripts): optional single-segment branch prefix for gitflow

- Add spec_kit_effective_branch_name / Get-SpecKitEffectiveBranchName:
  when branch matches prefix/rest with exactly one slash, validate and
  resolve specs/ using only the rest (e.g. feat/001-my-feature).
- Wire into check_feature_branch, find_feature_dir_by_prefix (bash) and
  Test-FeatureBranch, Find-FeatureDirByPrefix + Get-FeaturePathsEnv (PS).
- Align git extension git-common with core validation; remove unused
  get_feature_dir / Get-FeatureDir helpers.
- Extend tests in test_timestamp_branches.py and test_git_extension.py.

Made-with: Cursor

* Update scripts/powershell/common.ps1

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix(ps): align feature-dir resolution errors with bash (no throw under Stop)

Find-FeatureDirByPrefix: on ambiguous prefix matches, write errors to stderr
and return $null instead of throwing, matching find_feature_dir_by_prefix.

Get-FeaturePathsEnv: narrow try/catch to ConvertFrom-Json only; add
Get-FeatureDirFromBranchPrefixOrExit to mirror bash get_feature_paths
(stderr + exit 1) when prefix lookup fails, avoiding unhandled terminating
errors under $ErrorActionPreference = 'Stop' in check-prerequisites,
setup-plan, and update-agent-context.

Made-with: Cursor

* Update tests/test_timestamp_branches.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update extensions/git/scripts/powershell/git-common.ps1

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update scripts/powershell/common.ps1

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-13 18:11:17 -05:00
Manfred Riem
2972dec85c feat: Git extension stage 2 — GIT_BRANCH_NAME override, --force for existing dirs, auto-install tests (#1940) (#2117)
* 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)
2026-04-08 13:48:36 -05:00
Pascal THUET
838bd0fedc fix(git): surface checkout errors for existing branches (#2122) 2026-04-08 13:41:37 -05:00
Copilot
1a9e4d1d8d feat: Git extension stage 1 — bundled extensions/git with hooks on all core commands (#1941)
* feat: add git extension with hooks on all core commands

- Create extensions/git/ with 5 commands: initialize, feature,
  validate, remote, commit
- 18 hooks covering before/after for all 9 core commands
- Scripts: create-new-feature, initialize-repo, auto-commit,
  git-common (bash + powershell)
- Configurable: branch_numbering, init_commit_message,
  per-command auto-commit with custom messages
- Add hooks to analyze, checklist, clarify, constitution,
  taskstoissues command templates
- Allow hooks-only extensions (no commands required)
- Bundle extension in wheel via pyproject.toml force-include
- Resolve bundled extensions locally before catalog lookup
- Remove planned-but-unimplemented before/after_commit hook refs
- Update extension docs (API ref, dev guide, user guide)
- 37 new tests covering manifest, install, all scripts (bash+pwsh),
  config reading, graceful degradation

Stage 1: opt-in via 'specify extension add git'. No auto-install,
no changes to specify.md or core git init code.

Refs: #841, #1382, #1066, #1791, #1191

* fix: set git identity env vars in extension tests for CI runners

* fix: address PR review comments

- Fix commands property KeyError for hooks-only extensions
- Fix has_git() operator precedence in git-common.sh
- Align default commit message to '[Spec Kit] Initial commit' across
  config-template, extension.yml defaults, and both init scripts
- Update README to reflect all 5 commands and 18 hooks

* fix: address second round of PR review comments

- Add type validation for provides.commands (must be list) and hooks
  (must be dict) in manifest _validate()
- Tighten malformed timestamp detection in git-common.sh to catch
  7-digit dates without trailing slug (e.g. 2026031-143022)
- Pass REPO_ROOT to has_git/Test-HasGit in create-new-feature scripts
- Fix initialize command docs: surface errors on git failures, only
  skip when git is not installed
- Fix commit command docs: 'skips with a warning' not 'silently'
- Add tests for commands:null and hooks:list rejection

* fix: address third round of PR review comments

- Remove scripts frontmatter from command files (CommandRegistrar
  rewrites ../../scripts/ to .specify/scripts/ which points at core
  scripts, not extension scripts)
- Update speckit.git.commit command to derive event name from hook
  context rather than using a static example
- Clarify that hook argument passthrough works via AI agent context
  (the agent carries conversation state including user's original
  feature description)

* fix: address fourth round of PR review comments

- Validate extension_id against ^[a-z0-9-]+$ in _locate_bundled_extension
  to prevent path traversal (security fix)
- Move defaults under config.defaults in extension.yml to match
  ConfigManager._get_extension_defaults() schema
- Ship git-config.yml in extension directory so it's copied during
  install (provides.config template isn't materialized by ExtensionManager)
- Condition handling in hook templates: intentionally matches existing
  pattern from specify/plan/tasks/implement templates (not a new issue)

* fix: add --allow-empty to git commit in initialize-repo scripts

Ensures git init succeeds even on empty repos where nothing has been
staged yet.

* fix: resolve display names to bundled extensions before catalog download

When 'specify extension add "Git Branching Workflow"' is used with a
display name instead of the ID, the catalog resolver now runs first to
map the name to an ID, then checks bundled extensions again with the
resolved ID before falling back to network download.

Also noted: EXECUTE_COMMAND_INVOCATION and condition handling match the
existing pattern in specify/plan/tasks/implement templates (pre-existing,
not introduced by this PR).

* fix: handle before_/after_ prefixes in auto-commit message derivation

- Strip both before_ and after_ prefixes when deriving command name
  (fixes misleading 'Auto-commit after before_plan' messages)
- Include phase (before/after) in default commit messages
- Clarify README config example is an override, not default behavior

* fix: use portable grep -qw for word boundary in create-new-feature.sh

BSD grep (macOS) doesn't support \b as a word boundary. Replace with
grep -qw which is POSIX-portable.

* fix: validate hook values, numeric --number, and PS warning routing

- Validate each hook value is a dict with a 'command' field during
  manifest _validate() (prevents crash at install time)
- Validate --number is a non-negative integer in bash create-new-feature
  (clear error instead of cryptic shell arithmetic failure)
- Route PowerShell no-git warning to stderr in JSON mode so stdout
  stays valid JSON

---------

Co-authored-by: Manfred Riem <15701806+mnriem@users.noreply.github.com>
2026-04-07 08:39:35 -05:00