* 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)
6.0 KiB
Core Commands
The core specify commands handle project initialization, system checks, and version information.
Initialize a Project
specify init [<project_name>]
| Option | Description |
|---|---|
--integration <key> |
AI coding agent integration to use (e.g. copilot, claude, gemini). See the Integrations reference for all available keys |
--integration-options |
Options for the integration (e.g. --integration-options="--commands-dir .myagent/cmds") |
--script sh|ps |
Script type: sh (bash/zsh) or ps (PowerShell) |
--here |
Initialize in the current directory instead of creating a new one |
--force |
Force merge/overwrite when initializing in an existing directory |
--ignore-agent-tools |
Skip checks for AI coding agent CLI tools |
--preset <id> |
Install a preset during initialization |
Creates a new Spec Kit project with the necessary directory structure, templates, scripts, and AI coding agent integration files.
Note
Git repository initialization and branching are managed by the git extension, which is not installed by default. Run
specify extension add gitafter init to enable git workflows.
Use <project_name> to create a new directory, or --here (or .) to initialize in the current directory. If the directory already has files, use --force to merge without confirmation.
When --integration is omitted, interactive terminals prompt you to choose an integration. Non-interactive sessions, such as CI or piped runs, default to GitHub Copilot; pass --integration <key> to choose a different integration explicitly.
Examples
# Create a new project with an integration
specify init my-project --integration copilot
# Initialize in the current directory
specify init --here --integration copilot
# Force merge into a non-empty directory
specify init --here --force --integration copilot
# Use PowerShell scripts (Windows/cross-platform)
specify init my-project --integration copilot --script ps
# Install a preset during initialization
specify init my-project --integration copilot --preset compliance
Environment Variables
| Variable | Description |
|---|---|
SPECIFY_INIT_DIR |
Target a member project from outside its directory (e.g. a monorepo root) without cd, for non-interactive / CI use. Set it to the project root — the directory containing .specify/ (relative paths resolve against the current directory). The path must exist and contain .specify/, otherwise the command errors and does not fall back to the current directory. Resolved once in the core root helper (get_repo_root in Bash, Get-RepoRoot in PowerShell), so it is honored by the core feature scripts (/speckit.plan, /speckit.tasks, …) and the Git extension's feature-branch creation, which inherit it. The specify CLI applies the same validation rules to every project-scoped subcommand (specify integration …, specify extension …, specify workflow …, specify preset …, and the rest that operate on a .specify/ project), so those can target a member project too. When unset, Bash/PowerShell helpers keep their existing upward search; the specify CLI keeps its project-scoped resolver cwd-only unless a command explicitly defines broader detection (for example, bundle commands). |
SPECIFY_FEATURE_DIRECTORY |
Override the active feature directory within the resolved project (takes precedence over .specify/feature.json). Relative paths resolve under the project root. Combine with SPECIFY_INIT_DIR to pick both the project and the feature non-interactively. |
SPECIFY_FEATURE |
Override feature detection for non-Git repositories. Set to the feature directory name (e.g., 001-photo-albums) to work on a specific feature when not using Git branches. Must be set in the context of the agent prior to using /speckit.plan or follow-up commands. |
Two resolution axes.
SPECIFY_INIT_DIRselects the project (which directory contains.specify/);SPECIFY_FEATURE_DIRECTORY/.specify/feature.jsonselect the feature within that project. They are independent — project first, then feature.
Symlinked project roots.
SPECIFY_INIT_DIRrelocates where the project is, not how a command treats symlinks: each command keeps its existing cwd-path stance. Commands that traverse and write project files through broad input paths (bundle,workflow run <file>) refuse a symlinked.specify/to preserve write confinement. Other project-scoped commands keep their existing behavior whenSPECIFY_INIT_DIRpoints at a project root, which may include following a symlinked.specify/.
Check Installed Tools
specify check
Checks that CLI-based AI coding agents are available on your system. IDE-based agents are skipped since they don't require a CLI tool.
This command stays offline. If a command behaves like an older Spec Kit version or an expected CLI feature is missing, run specify self check to check whether your local CLI is behind the latest release.
Version Information
specify version
Displays the Spec Kit CLI version, Python version, platform, and architecture.
To inspect local CLI capabilities without checking the network:
specify version --features
specify version --features --json
The JSON form is intended for scripts and coding agents that need to choose a workflow based on the installed CLI's supported features.
A quick version check is also available via:
specify --version
specify -V