diff --git a/scripts/bash/setup-plan.sh b/scripts/bash/setup-plan.sh index f3edb3d9f8..aa394183cf 100644 --- a/scripts/bash/setup-plan.sh +++ b/scripts/bash/setup-plan.sh @@ -70,16 +70,16 @@ if $JSON_MODE; then jq -cn \ --arg feature_spec "$FEATURE_SPEC" \ --arg impl_plan "$IMPL_PLAN" \ - --arg specs_dir "$FEATURE_DIR" \ + --arg feature_dir "$FEATURE_DIR" \ --arg branch "$CURRENT_BRANCH" \ - '{FEATURE_SPEC:$feature_spec,IMPL_PLAN:$impl_plan,SPECS_DIR:$specs_dir,BRANCH:$branch}' + '{FEATURE_SPEC:$feature_spec,IMPL_PLAN:$impl_plan,FEATURE_DIR:$feature_dir,BRANCH:$branch}' else - printf '{"FEATURE_SPEC":"%s","IMPL_PLAN":"%s","SPECS_DIR":"%s","BRANCH":"%s"}\n' \ + printf '{"FEATURE_SPEC":"%s","IMPL_PLAN":"%s","FEATURE_DIR":"%s","BRANCH":"%s"}\n' \ "$(json_escape "$FEATURE_SPEC")" "$(json_escape "$IMPL_PLAN")" "$(json_escape "$FEATURE_DIR")" "$(json_escape "$CURRENT_BRANCH")" fi else echo "FEATURE_SPEC: $FEATURE_SPEC" echo "IMPL_PLAN: $IMPL_PLAN" - echo "SPECS_DIR: $FEATURE_DIR" + echo "FEATURE_DIR: $FEATURE_DIR" echo "BRANCH: $CURRENT_BRANCH" fi diff --git a/scripts/powershell/setup-plan.ps1 b/scripts/powershell/setup-plan.ps1 index 300582d5eb..036a54e4d1 100644 --- a/scripts/powershell/setup-plan.ps1 +++ b/scripts/powershell/setup-plan.ps1 @@ -76,13 +76,13 @@ if ($Json) { $result = [PSCustomObject]@{ FEATURE_SPEC = $paths.FEATURE_SPEC IMPL_PLAN = $paths.IMPL_PLAN - SPECS_DIR = $paths.FEATURE_DIR + FEATURE_DIR = $paths.FEATURE_DIR BRANCH = $paths.CURRENT_BRANCH } $result | ConvertTo-Json -Compress } else { Write-Output "FEATURE_SPEC: $($paths.FEATURE_SPEC)" Write-Output "IMPL_PLAN: $($paths.IMPL_PLAN)" - Write-Output "SPECS_DIR: $($paths.FEATURE_DIR)" + Write-Output "FEATURE_DIR: $($paths.FEATURE_DIR)" Write-Output "BRANCH: $($paths.CURRENT_BRANCH)" } diff --git a/scripts/python/setup_plan.py b/scripts/python/setup_plan.py index 3b8acc4fd4..721eed8f39 100644 --- a/scripts/python/setup_plan.py +++ b/scripts/python/setup_plan.py @@ -82,7 +82,7 @@ def main(argv: list[str] | None = None) -> int: { "FEATURE_SPEC": str(paths.feature_spec), "IMPL_PLAN": str(paths.impl_plan), - "SPECS_DIR": str(paths.feature_dir), + "FEATURE_DIR": str(paths.feature_dir), "BRANCH": paths.current_branch, } ) @@ -90,7 +90,7 @@ def main(argv: list[str] | None = None) -> int: else: print(f"FEATURE_SPEC: {paths.feature_spec}") print(f"IMPL_PLAN: {paths.impl_plan}") - print(f"SPECS_DIR: {paths.feature_dir}") + print(f"FEATURE_DIR: {paths.feature_dir}") print(f"BRANCH: {paths.current_branch}") return 0 diff --git a/templates/commands/plan.md b/templates/commands/plan.md index 664f428114..836e25070c 100644 --- a/templates/commands/plan.md +++ b/templates/commands/plan.md @@ -59,7 +59,7 @@ You **MUST** consider the user input before proceeding (if not empty). ## Outline -1. **Setup**: Run `{SCRIPT}` from repo root and parse JSON for FEATURE_SPEC, IMPL_PLAN, SPECS_DIR, BRANCH. For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot"). +1. **Setup**: Run `{SCRIPT}` from repo root and parse JSON for FEATURE_SPEC, IMPL_PLAN, FEATURE_DIR, BRANCH. For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot"). 2. **Load context**: Read FEATURE_SPEC and `/memory/constitution.md`. Load IMPL_PLAN template (already copied). diff --git a/tests/test_setup_plan_python_parity.py b/tests/test_setup_plan_python_parity.py index d66c7083b3..09356a142f 100644 --- a/tests/test_setup_plan_python_parity.py +++ b/tests/test_setup_plan_python_parity.py @@ -374,3 +374,57 @@ def test_python_json_output_matches_powershell(repo: Path) -> None: assert py.returncode == ps.returncode == 0 assert json_stdout(py) == json_stdout(ps) + + +@requires_bash +@pytest.mark.parametrize("args", [("--json",), ()], ids=["json", "text"]) +def test_all_variants_emit_feature_dir_not_specs_dir( + repo: Path, args: tuple[str, ...] +) -> None: + r"""Pin the output key name, not just cross-port agreement. + + The other tests here compare the ports against each other, so all three + could regress to ``SPECS_DIR`` together and still pass. This asserts the + contract absolutely: the key is ``FEATURE_DIR``, it carries the feature + directory rather than the specs root, and the old name is gone. + ``SPECS_DIR`` means the specs root in ``create-new-feature.sh``, so + re-emitting it here would reintroduce one name for two paths. + + The value is matched by suffix because the ports legitimately differ in + path flavour -- under MSYS bash reports ``/tmp/...`` where the Python and + PowerShell ports report ``C:\...``. The suffix still separates + ``specs/001-my-feature`` from a bare ``specs``, which is the regression + this guards. + """ + json_mode = args == ("--json",) + suffix = ("specs", "001-my-feature") + + commands = [bash_cmd(repo, SCRIPT, *args), py_cmd(repo, SCRIPT, *args)] + if HAS_POWERSHELL: + commands.append(ps_cmd(repo, SCRIPT, *(("-Json",) if json_mode else ()))) + + for cmd in commands: + result = run(cmd, repo) + assert result.returncode == 0, result.stderr + assert "SPECS_DIR" not in result.stdout + + if json_mode: + payload = json_stdout(result) + assert isinstance(payload, dict) + assert sorted(payload) == [ + "BRANCH", + "FEATURE_DIR", + "FEATURE_SPEC", + "IMPL_PLAN", + ] + value = payload["FEATURE_DIR"] + else: + lines = dict( + line.split(": ", 1) + for line in result.stdout.splitlines() + if ": " in line + ) + assert "FEATURE_DIR" in lines + value = lines["FEATURE_DIR"] + + assert tuple(value.replace("\\", "/").rstrip("/").split("/")[-2:]) == suffix