Skip to content

Commit 7e92dfb

Browse files
committed
refactor(scripts): rename SPECIFY_NO_PERSIST to SPECIFY_FEATURE_NO_PERSIST
Per review feedback on #4129, align the env var name with SPECIFY_FEATURE_DIRECTORY across bash/powershell/python.
1 parent bc3341a commit 7e92dfb

8 files changed

Lines changed: 24 additions & 24 deletions

File tree

‎docs/reference/core.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ specify init my-project --integration copilot --preset compliance
5454
| `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). |
5555
| `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. |
5656
| `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. |
57-
| `SPECIFY_NO_PERSIST` | Set to `1` or `true` to stop every core script from writing `.specify/feature.json`, even when it would otherwise persist `SPECIFY_FEATURE_DIRECTORY` on read. Useful when multiple agents run concurrently against the same checkout, each with its own `SPECIFY_FEATURE_DIRECTORY`: without it, each invocation's persist step can overwrite another agent's pinned feature directory. |
57+
| `SPECIFY_FEATURE_NO_PERSIST` | Set to `1` or `true` to stop every core script from writing `.specify/feature.json`, even when it would otherwise persist `SPECIFY_FEATURE_DIRECTORY` on read. Useful when multiple agents run concurrently against the same checkout, each with its own `SPECIFY_FEATURE_DIRECTORY`: without it, each invocation's persist step can overwrite another agent's pinned feature directory. |
5858

5959
> **Two resolution axes.** `SPECIFY_INIT_DIR` selects the **project** (which directory contains `.specify/`); `SPECIFY_FEATURE_DIRECTORY` / `.specify/feature.json` select the **feature** within that project. They are independent — project first, then feature.
6060

‎scripts/bash/common.sh‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,11 +169,11 @@ get_feature_paths() {
169169
no_persist=true
170170
shift
171171
fi
172-
# SPECIFY_NO_PERSIST is the environment-level equivalent of --no-persist,
172+
# SPECIFY_FEATURE_NO_PERSIST is the environment-level equivalent of --no-persist,
173173
# letting an orchestrator (multi-agent runner, CI matrix) guarantee that no
174174
# script invocation in the process tree writes .specify/feature.json, even
175175
# scripts that don't pass --no-persist themselves (#4128).
176-
if [[ "${SPECIFY_NO_PERSIST:-}" == "1" || "${SPECIFY_NO_PERSIST:-}" == "true" ]]; then
176+
if [[ "${SPECIFY_FEATURE_NO_PERSIST:-}" == "1" || "${SPECIFY_FEATURE_NO_PERSIST:-}" == "true" ]]; then
177177
no_persist=true
178178
fi
179179

‎scripts/bash/create-new-feature.sh‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -367,8 +367,8 @@ if [ "$DRY_RUN" != true ]; then
367367
fi
368368

369369
# Persist to .specify/feature.json so downstream commands can find the
370-
# feature, unless the orchestrator opted out via SPECIFY_NO_PERSIST (#4129).
371-
if [[ "${SPECIFY_NO_PERSIST:-}" != "1" && "${SPECIFY_NO_PERSIST:-}" != "true" ]]; then
370+
# feature, unless the orchestrator opted out via SPECIFY_FEATURE_NO_PERSIST (#4129).
371+
if [[ "${SPECIFY_FEATURE_NO_PERSIST:-}" != "1" && "${SPECIFY_FEATURE_NO_PERSIST:-}" != "true" ]]; then
372372
_persist_feature_json "$REPO_ROOT" "$FEATURE_DIR"
373373
fi
374374

‎scripts/powershell/common.ps1‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,11 +166,11 @@ function Get-FeaturePathsEnv {
166166
[switch]$ReturnNullOnError
167167
)
168168

169-
# SPECIFY_NO_PERSIST is the environment-level equivalent of -NoPersist,
169+
# SPECIFY_FEATURE_NO_PERSIST is the environment-level equivalent of -NoPersist,
170170
# letting an orchestrator (multi-agent runner, CI matrix) guarantee that no
171171
# script invocation in the process tree writes .specify/feature.json, even
172172
# scripts that don't pass -NoPersist themselves (#4128).
173-
$noPersist = [bool]$NoPersist -or $env:SPECIFY_NO_PERSIST -eq '1' -or $env:SPECIFY_NO_PERSIST -eq 'true'
173+
$noPersist = [bool]$NoPersist -or $env:SPECIFY_FEATURE_NO_PERSIST -eq '1' -or $env:SPECIFY_FEATURE_NO_PERSIST -eq 'true'
174174

175175
$repoRoot = Get-RepoRoot -ReturnNullOnError:$ReturnNullOnError
176176
if (-not $repoRoot) { return $null }

‎scripts/powershell/create-new-feature.ps1‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -284,8 +284,8 @@ if (-not $DryRun) {
284284
}
285285

286286
# Persist to .specify/feature.json so downstream commands can find the
287-
# feature, unless the orchestrator opted out via SPECIFY_NO_PERSIST (#4129).
288-
if ($env:SPECIFY_NO_PERSIST -ne '1' -and $env:SPECIFY_NO_PERSIST -ne 'true') {
287+
# feature, unless the orchestrator opted out via SPECIFY_FEATURE_NO_PERSIST (#4129).
288+
if ($env:SPECIFY_FEATURE_NO_PERSIST -ne '1' -and $env:SPECIFY_FEATURE_NO_PERSIST -ne 'true') {
289289
Save-FeatureJson -RepoRoot $repoRoot -FeatureDirectory $featureDir
290290
}
291291

‎scripts/python/common.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,11 +139,11 @@ def get_feature_paths(
139139
repo_root = get_repo_root(script_file)
140140
current_branch = get_current_branch()
141141

142-
# SPECIFY_NO_PERSIST is the environment-level equivalent of no_persist=True,
142+
# SPECIFY_FEATURE_NO_PERSIST is the environment-level equivalent of no_persist=True,
143143
# letting an orchestrator (multi-agent runner, CI matrix) guarantee that no
144144
# script invocation in the process tree writes .specify/feature.json, even
145145
# scripts that don't pass no_persist themselves (#4128).
146-
no_persist = no_persist or os.environ.get("SPECIFY_NO_PERSIST", "") in ("1", "true")
146+
no_persist = no_persist or os.environ.get("SPECIFY_FEATURE_NO_PERSIST", "") in ("1", "true")
147147

148148
feature_dir_raw = os.environ.get("SPECIFY_FEATURE_DIRECTORY", "")
149149
if feature_dir_raw:

‎scripts/python/create_new_feature.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -408,8 +408,8 @@ def main(argv: list[str] | None = None) -> int:
408408
spec_file.touch()
409409

410410
# Persist to .specify/feature.json so downstream commands can find the
411-
# feature, unless the orchestrator opted out via SPECIFY_NO_PERSIST (#4129).
412-
if os.environ.get("SPECIFY_NO_PERSIST", "") not in ("1", "true"):
411+
# feature, unless the orchestrator opted out via SPECIFY_FEATURE_NO_PERSIST (#4129).
412+
if os.environ.get("SPECIFY_FEATURE_NO_PERSIST", "") not in ("1", "true"):
413413
persist_feature_json(repo_root, f"specs/{branch_name}")
414414

415415
# Inform the user how to set feature state in their own shell.

‎tests/test_specify_no_persist.py‎

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
"""Tests for SPECIFY_NO_PERSIST, the env-level equivalent of --no-persist (#4128).
1+
"""Tests for SPECIFY_FEATURE_NO_PERSIST, the env-level equivalent of --no-persist (#4128).
22
33
Scripts like setup-plan/setup-tasks call get_feature_paths() without
44
--no-persist, so every invocation with SPECIFY_FEATURE_DIRECTORY set
55
overwrites .specify/feature.json. In multi-agent setups where several
66
processes each set their own SPECIFY_FEATURE_DIRECTORY, this creates a
7-
write-write race on the shared file. SPECIFY_NO_PERSIST lets an orchestrator
7+
write-write race on the shared file. SPECIFY_FEATURE_NO_PERSIST lets an orchestrator
88
suppress that write across every script invocation without having to patch
99
each call site.
1010
"""
@@ -68,7 +68,7 @@ def test_bash_specify_no_persist_suppresses_write(repo: Path) -> None:
6868
(repo / "specs" / "001-a").mkdir(parents=True)
6969
env = clean_env()
7070
env["SPECIFY_FEATURE_DIRECTORY"] = "specs/001-a"
71-
env["SPECIFY_NO_PERSIST"] = "1"
71+
env["SPECIFY_FEATURE_NO_PERSIST"] = "1"
7272
result = run(bash_cmd(repo, SCRIPT, "--json"), repo, env)
7373
assert result.returncode == 0, result.stderr
7474
assert _feature_json(repo) is None
@@ -77,7 +77,7 @@ def test_bash_specify_no_persist_suppresses_write(repo: Path) -> None:
7777
@requires_bash
7878
def test_bash_specify_no_persist_does_not_clobber_existing_pin(repo: Path) -> None:
7979
"""A second agent's SPECIFY_FEATURE_DIRECTORY must not overwrite the
80-
first agent's persisted feature.json when SPECIFY_NO_PERSIST is set."""
80+
first agent's persisted feature.json when SPECIFY_FEATURE_NO_PERSIST is set."""
8181
(repo / "specs" / "001-a").mkdir(parents=True)
8282
(repo / "specs" / "002-b").mkdir(parents=True)
8383
env = clean_env()
@@ -87,7 +87,7 @@ def test_bash_specify_no_persist_does_not_clobber_existing_pin(repo: Path) -> No
8787
assert _feature_json(repo) == {"feature_directory": "specs/001-a"}
8888

8989
env["SPECIFY_FEATURE_DIRECTORY"] = "specs/002-b"
90-
env["SPECIFY_NO_PERSIST"] = "1"
90+
env["SPECIFY_FEATURE_NO_PERSIST"] = "1"
9191
result = run(bash_cmd(repo, SCRIPT, "--json"), repo, env)
9292
assert result.returncode == 0, result.stderr
9393
assert _feature_json(repo) == {"feature_directory": "specs/001-a"}
@@ -98,7 +98,7 @@ def test_ps_specify_no_persist_suppresses_write(repo: Path) -> None:
9898
(repo / "specs" / "001-a").mkdir(parents=True)
9999
env = clean_env()
100100
env["SPECIFY_FEATURE_DIRECTORY"] = "specs/001-a"
101-
env["SPECIFY_NO_PERSIST"] = "true"
101+
env["SPECIFY_FEATURE_NO_PERSIST"] = "true"
102102
result = run(ps_cmd(repo, SCRIPT, "-Json"), repo, env)
103103
assert result.returncode == 0, result.stderr
104104
assert _feature_json(repo) is None
@@ -108,14 +108,14 @@ def test_py_specify_no_persist_suppresses_write(repo: Path) -> None:
108108
(repo / "specs" / "001-a").mkdir(parents=True)
109109
env = clean_env()
110110
env["SPECIFY_FEATURE_DIRECTORY"] = "specs/001-a"
111-
env["SPECIFY_NO_PERSIST"] = "1"
111+
env["SPECIFY_FEATURE_NO_PERSIST"] = "1"
112112
result = run(py_cmd(repo, SCRIPT, "--json"), repo, env)
113113
assert result.returncode == 0, result.stderr
114114
assert _feature_json(repo) is None
115115

116116

117117
# create-new-feature writes .specify/feature.json directly (not via
118-
# get_feature_paths()), so it must honor SPECIFY_NO_PERSIST separately.
118+
# get_feature_paths()), so it must honor SPECIFY_FEATURE_NO_PERSIST separately.
119119

120120
CREATE_SCRIPT = "create-new-feature"
121121
SPEC_TEMPLATE_BODY = "# Spec Template\n\nBody.\n"
@@ -134,7 +134,7 @@ def _create_feature_repo(tmp_path: Path, name: str = "proj") -> Path:
134134
def test_bash_create_new_feature_no_persist_suppresses_write(tmp_path: Path) -> None:
135135
repo = _create_feature_repo(tmp_path)
136136
env = clean_env()
137-
env["SPECIFY_NO_PERSIST"] = "1"
137+
env["SPECIFY_FEATURE_NO_PERSIST"] = "1"
138138
result = run(bash_cmd(repo, CREATE_SCRIPT, "--json", "x"), repo, env)
139139
assert result.returncode == 0, result.stderr
140140
assert _feature_json(repo) is None
@@ -144,7 +144,7 @@ def test_bash_create_new_feature_no_persist_suppresses_write(tmp_path: Path) ->
144144
def test_ps_create_new_feature_no_persist_suppresses_write(tmp_path: Path) -> None:
145145
repo = _create_feature_repo(tmp_path)
146146
env = clean_env()
147-
env["SPECIFY_NO_PERSIST"] = "true"
147+
env["SPECIFY_FEATURE_NO_PERSIST"] = "true"
148148
result = run(ps_cmd(repo, CREATE_SCRIPT, "-Json", "x"), repo, env)
149149
assert result.returncode == 0, result.stderr
150150
assert _feature_json(repo) is None
@@ -153,7 +153,7 @@ def test_ps_create_new_feature_no_persist_suppresses_write(tmp_path: Path) -> No
153153
def test_py_create_new_feature_no_persist_suppresses_write(tmp_path: Path) -> None:
154154
repo = _create_feature_repo(tmp_path)
155155
env = clean_env()
156-
env["SPECIFY_NO_PERSIST"] = "1"
156+
env["SPECIFY_FEATURE_NO_PERSIST"] = "1"
157157
result = run(py_cmd(repo, CREATE_SCRIPT, "--json", "x"), repo, env)
158158
assert result.returncode == 0, result.stderr
159159
assert _feature_json(repo) is None

0 commit comments

Comments
 (0)