Skip to content

fix(powershell): treat project and preset template paths literally - #5396

Open
kartsan03 wants to merge 4 commits into
github:mainfrom
kartsan03:fix/powershell-literal-template-paths
Open

kartsan03 wants to merge 4 commits into
github:mainfrom
kartsan03:fix/powershell-literal-template-paths

Conversation

@kartsan03

@kartsan03 kartsan03 commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

Description

In a project whose path contains [ or ], the PowerShell helpers behind /speckit.specify, /speckit.plan, /speckit.tasks and the commands that call check-prerequisites all exit 1, while the Bash and Python helpers work. scripts/powershell/common.ps1 passes the project's paths to Test-Path and Get-ChildItem with -Path, which reads brackets as wildcards. So .specify/feature.json, the template directories and the preset registry all look missing. #4956 and #4970 fixed this for bracketed feature directories. This PR does the same for the project root and the template stack.

The same problem also hits a preset whose manifest declares a template file with brackets in its name (file: templates/custom [v1].md), even when the project path has none. resolve-template.ps1 skips that file and returns the stock template, and /speckit.specify writes it to spec.md.

The fix switches those calls to -LiteralPath in Get-FeaturePathsEnv, Get-SortedExtensionIds, Resolve-Template and Resolve-TemplateContent, along with the cleanup of the temporary stderr file in Resolve-TemplateContent. The Bash and Python helpers already handle these paths and are unchanged.

Repro with the real CLI: inside a directory named work [2026], run specify init demo --integration codex --script ps --ignore-agent-tools --non-interactive, add specs/001-demo/spec.md, and point .specify/feature.json at specs/001-demo. Then, from the project root:

PowerShell helper main (979b0a3) This PR
resolve-template.ps1 spec-template -Json exit 1, ERROR: Could not resolve required spec-template from the template override stack for … exit 0, stock spec template
setup-plan.ps1 -Json exit 1, ERROR: Feature directory not found. Set SPECIFY_FEATURE_DIRECTORY or run the specify command to create .specify/feature.json. exit 0, copies the plan template
check-prerequisites.ps1 -Json exit 1, same error exit 0
setup-tasks.ps1 -Json (after adding plan.md) exit 1, same error exit 0

Testing

  • Tested locally with uv run specify --help
  • Ran existing tests with uv sync && uv run pytest
  • Tested with a sample project (if applicable)

New parity tests run the Python and PowerShell helpers on the same project, plus Bash where tests/conftest.py finds a working one, and compare the results. They check the new HAS_BASH flag (the probe behind requires_bash) instead of using requires_bash, which would skip them on windows-latest, PowerShell runs included:

  • test_all_variants_resolve_templates_in_bracketed_project (tests/test_resolve_template_python_parity.py): a project under project [v1], once for each template layer (core, override, preset with and without a registry, flat preset and extension layouts, preset manifest, missing template). One more case puts the bracketed manifest file name in a plain project path. The manifest cases parse preset.yml with Python, so the test sets SPECKIT_PYTHON_EXECUTABLE to the test interpreter.
  • test_powershell_bracketed_manifest_reports_missing_python: in a bracketed project, the PowerShell resolver still finds the preset manifest and asks for Python when none is on PATH.
  • test_all_variants_setup_tasks_in_bracketed_project (tests/test_setup_tasks_python_parity.py): the feature comes from .specify/feature.json or from SPECIFY_FEATURE_DIRECTORY. The test checks FEATURE_DIR, TASKS_TEMPLATE and its content, and that the existing spec.md, plan.md and tasks.md are left byte for byte as they were.

With common.ps1 from main, 27 of the 28 new cases fail. The missing-template case passes on both. The same holds on Windows PowerShell 5.1.26100, run on a windows-latest runner in my fork (run): 28 pass on this branch, and 27 fail with common.ps1 from main.

Full suite on Linux, Python 3.13 (pytest -n 3, 12 min, with the venv and pwsh on PATH): 16580 passed, 252 skipped, 1 failed. The failure, test_version_execution.py::TestWindowsUvToolEnvironment::test_non_windows_uv_tool_dry_run_still_previews_installer, fails the same way on main here: Rich wraps the dry-run output and splits my checkout's long venv path. Ruff 0.15.0 (ruff check src tests) passes. PowerShell 7.6.9 on Linux, and Windows PowerShell 5.1 as above. I haven't run the full suite on Windows.

AI Disclosure

  • I did not use AI assistance for this contribution
  • I did use AI assistance (fill in the disclosure below)

AI disclosure: Codex CLI (model: gpt-6.1-sol, reasoning effort xhigh, autonomous, offline sandbox) found the failures by running the same inputs through the Bash, PowerShell and Python helpers, and wrote the fix and tests. Claude Code (model: claude-opus-5-5, autonomous) also made the temporary-file cleanup literal, reran the repro with the real CLI on main and the branch, ran the full suite and lint, and drafted this description. The commit carries Assisted-by: trailers for both.

common.ps1 passed project, preset and extension paths to Test-Path and
Get-ChildItem through -Path, which reads [ and ] as wildcards. In a
project whose path contains brackets, resolve-template, setup-plan,
setup-tasks and check-prerequisites exited 1 while the Bash and Python
scripts succeeded. A preset manifest whose declared file name has
brackets was skipped, so the stock template was used instead.

Use -LiteralPath, as github#4956 and github#4970 did for feature paths.

Assisted-by: Codex CLI (model: gpt-6.1-sol, autonomous)
Assisted-by: Claude Code (model: claude-opus-5-5, autonomous)
@kartsan03
kartsan03 requested a review from mnriem as a code owner November 12, 2026 02:26
Copilot AI auto review requested due to automatic review settings November 12, 2026 02:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The parity test incorrectly compares Git Bash MSYS paths with native Windows paths, causing successful Bash invocations to fail assertions before PowerShell is tested.

1 open issue
What changed in this PR

Extends the earlier bracketed-feature-path fixes to project roots and preset templates, keeping PowerShell helpers aligned with Bash and Python.

Changes:

  • Uses literal paths for feature state, template lookup, directory enumeration, and temporary-file cleanup.
  • Adds template-resolution parity tests across override, preset, extension, and core layers.
  • Adds task-setup regressions covering feature selection and preservation of existing files.
File Description
tests/​test_setup_tasks_python_parity.py Tests bracketed project paths, template selection, and file preservation.
tests/​test_resolve_template_python_parity.py Tests bracketed project and manifest paths, missing templates, and missing Python.
scripts/​powershell/​common.ps1 Replaces wildcard-aware path operations with literal-path operations.

Review effort: Balanced (auto)

Comment thread tests/test_setup_tasks_python_parity.py Resolved
Git Bash on Windows can print MSYS paths such as /c/Users/... for
FEATURE_DIR and TASKS_TEMPLATE, which never equal the native paths the
test built. For the Bash run, also accept the form _bash_posix_path()
gives; Python and PowerShell output is still compared with the native
path.

Assisted-by: Claude Code (model: claude-opus-5-5, autonomous)
Copilot AI auto review requested due to automatic review settings November 12, 2026 02:37
@kartsan03

Copy link
Copy Markdown
Contributor Author

Round 1 (27247ff): Copilot's Git Bash path finding in test_all_variants_setup_tasks_in_bracketed_project is addressed. The Bash run may now return either the native path or the MSYS form from _bash_posix_path(), and Python and PowerShell are still checked against the native path. The resolve-template test only compares TEMPLATE_CONTENT, so it needed no change.

tests/test_resolve_template_python_parity.py and tests/test_setup_tasks_python_parity.py: 111 passed on Linux with PowerShell 7.6.9. Ruff 0.15.0 passes. Not run on Windows.

Drafted on behalf of @kartsan03 by Claude Code (model: claude-opus-5-5, autonomous).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Required Windows PowerShell 5.1 compatibility evidence is missing, so a maintainer needs to validate the affected bracketed-path success and failure cases on that runtime.

No open issues 🎉

1 resolved since last review

Review effort: Balanced (auto)

requires_bash skipped each whole test, PowerShell and Python runs
included, wherever bash isn't detected. That covers windows-latest,
where the WSL launcher is rejected, so the PowerShell fix had no
Windows coverage. Run Python and PowerShell always, and Bash when
tests/conftest.py's new HAS_BASH says it works.

Assisted-by: Claude Code (model: claude-opus-5-5, autonomous)
… test

The manifest cases make the Bash and PowerShell resolvers parse
preset.yml with Python. On a Windows runner, PowerShell picked an
ambient python3.exe without PyYAML and failed with yaml_missing. Set
SPECKIT_PYTHON_EXECUTABLE to the test interpreter, as
test_resolve_template_python_parity.py already does elsewhere.

Assisted-by: Claude Code (model: claude-opus-5-5, autonomous)
Copilot AI auto review requested due to automatic review settings November 12, 2026 03:12
@kartsan03

Copy link
Copy Markdown
Contributor Author

Round 2 (d4ad7fd): Copilot's last pass asked for Windows PowerShell 5.1 evidence.

I ran the 28 new cases with Windows PowerShell 5.1.26100 on a windows-latest runner in my fork: all pass on this branch, and 27 fail with common.ps1 from main (979b0a3). Run: https://github.com/kartsan03/spec-kit/actions/runs/43145030696. That workflow lives on a branch in my fork, not in this PR.

The run needed two test changes, now pushed here:

  • 0ce3730: requires_bash skipped each new test whole on windows-latest, PowerShell runs included, because the bash probe there rejects the runner's bash. The tests now always run the Python and PowerShell helpers, and add Bash when tests/conftest.py's new HAS_BASH is true. It's the same probe requires_bash uses.
  • d4ad7fd: the preset manifest cases set SPECKIT_PYTHON_EXECUTABLE to the test interpreter. Without it, PowerShell on the runner picked a python3.exe without PyYAML and stopped with yaml_missing.

Full suite on Linux with PowerShell 7.6.9 on PATH: 16580 passed, 252 skipped, plus the one failure that also fails on main here (test_non_windows_uv_tool_dry_run_still_previews_installer, long venv path). Ruff passes. The PR description is updated to match.

Drafted on behalf of @kartsan03 by Claude Code (model: claude-opus-5-5, autonomous).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No active review issues were identified in the bracketed-path handling changes and accompanying regression coverage.

No open issues 🎉

Review effort: Balanced (auto)

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants