Resolve the ``SPECIFY_INIT_DIR`` project override for the Python CLI. Applies the same validation rules as the shell resolver (``resolve_specify_init_dir`` in ``scripts/bash/common.sh``): the value names the project root — the directory *containing* ``.specify/`` — and is strict. Re
()
| 11 | |
| 12 | |
| 13 | def _resolve_init_dir_override() -> Path | None: |
| 14 | """Resolve the ``SPECIFY_INIT_DIR`` project override for the Python CLI. |
| 15 | |
| 16 | Applies the same validation rules as the shell resolver |
| 17 | (``resolve_specify_init_dir`` in ``scripts/bash/common.sh``): the value names |
| 18 | the project root — the directory *containing* ``.specify/`` — and is strict. |
| 19 | Relative paths resolve against the current directory; the path must exist and |
| 20 | contain ``.specify/``, otherwise this hard-errors with no fallback to cwd |
| 21 | (which would silently operate on the wrong project's files). The error |
| 22 | messages mirror the shell resolver's wording (rendered here as a Rich |
| 23 | ``Error:`` line, plain ``ERROR:`` in the shell) so the two surfaces read |
| 24 | consistently. |
| 25 | |
| 26 | Returns the validated absolute project root, or ``None`` when the variable is |
| 27 | unset/empty, in which case callers keep their existing cwd-based behavior. |
| 28 | |
| 29 | Note: this canonicalizes symlinks via :meth:`Path.resolve` (physical path), |
| 30 | whereas the shell ``cd -- "$X" && pwd`` keeps the logical path. The two agree |
| 31 | for non-symlinked paths; a symlinked ``SPECIFY_INIT_DIR`` can resolve to |
| 32 | different strings across the surfaces. The canonical form is the safer choice |
| 33 | here (a stable project identity), so this is a deliberate, documented variance, |
| 34 | not a parity guarantee on the resolved string. |
| 35 | """ |
| 36 | raw = os.environ.get("SPECIFY_INIT_DIR", "") |
| 37 | if not raw: |
| 38 | return None |
| 39 | # Relative values resolve against cwd; an absolute value stands alone (Path's |
| 40 | # `/` drops the left operand when the right is absolute). resolve() also |
| 41 | # collapses a trailing slash and canonicalizes symlinks. |
| 42 | init_root = (Path.cwd() / raw).resolve() |
| 43 | if not init_root.is_dir(): |
| 44 | err_console.print( |
| 45 | f"[red]Error:[/red] SPECIFY_INIT_DIR does not point to an existing directory: {raw}" |
| 46 | ) |
| 47 | raise typer.Exit(1) |
| 48 | if not (init_root / ".specify").is_dir(): |
| 49 | err_console.print( |
| 50 | f"[red]Error:[/red] SPECIFY_INIT_DIR is not a Spec Kit project (no .specify/ directory): {init_root}" |
| 51 | ) |
| 52 | raise typer.Exit(1) |
| 53 | return init_root |
no test coverage detected