* feat(cli): honor SPECIFY_INIT_DIR in the specify CLI project resolver
The shell resolver honors SPECIFY_INIT_DIR (#2892), but the Python CLI did
not: it resolved the project as Path.cwd() + a .specify/ check and never read
the override. So setup-plan.sh respected it while `specify integration install`
ignored it, and you still had to cd into the member project.
Route project resolution through a shared _resolve_init_dir_override() that
applies the shell resolver's validation rules (relative to cwd, must exist and
contain .specify/, hard error, no fallback, same error strings). It's wired into
_require_specify_project() — the chokepoint for every project-scoped subcommand
(integration/extension/workflow/preset/...) — and the `workflow run <file>`
standalone path, which re-applies its symlinked-.specify guard on the override
branch too. init is unchanged: it creates .specify/, so the must-pre-exist rule
doesn't apply.
The resolver canonicalizes symlinks via Path.resolve() while the shell keeps the
logical path; they agree for non-symlinked paths (documented in the resolver).
Tests in tests/test_init_dir_cli.py mirror the strict cases from test_init_dir.py
through the CLI; conftest now strips SPECIFY_* for the whole suite so a stray
export can't perturb the now-env-reading resolver. Docs note the CLI applies the
same rules.
Discussion: github/spec-kit#2834
(Disclosure: I used an AI coding agent to audit the call sites and resolver,
draft the change, and run an adversarial code review; reviewed by me.)
* fix(cli): honor SPECIFY_INIT_DIR for bundle commands
Assisted-by: Codex (model: GPT-5, autonomous)
* fix(bundler): refuse symlinked .specify on the SPECIFY_INIT_DIR override path
find_project_root refuses a symlinked .specify (following it could read/write
outside the tree, and a test pins that), but the SPECIFY_INIT_DIR override added
for bundle commands returned early and skipped that guard:
_resolve_init_dir_override validates .specify with is_dir(), which follows
symlinks. So `specify bundle` accepted via the override a layout the cwd path
rejects. Re-check the override result with the same guard, plus a regression test.
(Disclosure: found via an AI code review and fixed with an AI coding agent;
reviewed by me.)
* fix(cli): keep SPECIFY_INIT_DIR strict for bundles
Treat an explicit symlinked SPECIFY_INIT_DIR project as a hard bundle error instead of returning no project, which could initialize the current directory. Align the docs with the actual unset resolver behavior.
Assisted-by: Codex (model: GPT-5, autonomous)
* docs(core): note symlinked .specify handling differs across CLI surfaces
A symlinked .specify is followed by integration/extension/workflow (matching the
shell resolver) but refused by bundle and workflow run <file> (write
confinement). Document the asymmetry so it reads as intentional.
(Disclosure: AI-assisted; reviewed by me.)
* docs(core): reframe symlinked .specify note around the override invariant
Per maintainer feedback on #3186: SPECIFY_INIT_DIR relocates where the project
is, not how a surface treats symlinks. Each surface keeps its cwd-path stance
(write surfaces refuse a symlinked .specify, read/config surfaces follow it),
so the split is one policy relocated, not an inconsistency.
* docs: address Copilot review on resolver docstrings
- _project.py: the error messages "mirror" the shell wording rather than
"match" it (the CLI renders a Rich `Error:` line, the shell a plain `ERROR:`).
- find_project_root: document that honoring SPECIFY_INIT_DIR when start is None
can raise typer.Exit / BundlerError, so the Path | None signature isn't
surprising to direct callers.
* docs(bundler): note require_project_root inherits the override raise behavior
find_project_root can raise typer.Exit / BundlerError under the SPECIFY_INIT_DIR
override (start=None); require_project_root inherits that, so document it
alongside its own BundlerError-on-missing-project.
* docs: clarify symlinked project root behavior
Assisted-by: OpenAI Codex (model: GPT-5, autonomous)
* Address SPECIFY_INIT_DIR review feedback
Assisted-by: OpenAI Codex (model: GPT-5, autonomous)
* Route workflow JSON errors to stderr
Assisted-by: OpenAI Codex (model: GPT-5, autonomous)
* 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
* 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>
* feat: add self-check tip to check output
* style: drop trailing period from self-check tip
Aligns the new tip with the other `Tip:` lines in `specify check`,
which don't end in a period. Per Copilot review feedback on #2574.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs: add core commands reference and simplify README CLI section
- New docs/reference/core.md: reference for init (active options only,
copilot as main example), check, and version commands
- docs/toc.yml: add Core Commands under Reference
- README.md: replace verbose CLI Reference section (init options table,
30+ per-agent examples, deprecated flags, env vars) with links to
reference docs; use copilot as main example throughout
* docs: add CLI reference overview page
- New docs/reference/overview.md: explains each CLI surface area
(core, integrations, extensions, presets, workflows) with key
commands and links to detailed reference pages
- docs/toc.yml: add Overview as first item under Reference
- README.md: simplify CLI Reference to single link to overview page
* docs: remove command references from overview, keep paragraphs only