mirror of
https://github.com/github/spec-kit.git
synced 2026-08-15 14:02:04 +02:00
* fix(init): escape user-supplied values in `specify init` output
commands/init.py interpolated the project name, --integration/--script values
and paths straight into Rich markup f-strings. It was the only CLI command
module without escaping -- extensions, presets, workflows and integrations all
wrap user-controlled display values already.
Two consequences, both reproduced end-to-end through the real CLI:
1. SILENT WRONG OUTPUT. `specify init "proj [v2]"` exits 0 and creates the
directory, but the Next Steps panel prints
1. Go to the project folder: cd proj
Rich ate `[v2]` as a style tag, so the command the user copy-pastes fails.
2. CRASH AFTER SUCCESS. `specify init "app[/red]x"` creates the project and
then dies with MarkupError("closing tag '[/red]' ... doesn't match any open
tag") -> exit 1 with a traceback for work that actually completed.
Wrap the user-controlled display values in rich.markup.escape: project name
(error/warning/conflict/next-steps), project and working paths, the echoed
--integration and --script values, and the agent folder in the gitignore hint.
Display only -- no control flow, exit codes or messages change, and escape is a
no-op for any value without a tag-shaped bracket run.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(init): shell-quote the project name in the Next Steps cd line
Rich-escaping stopped the brackets being swallowed, but the printed
command was still unusable for any name containing whitespace: `cd proj
v2` is two arguments in every shell.
$ cd proj v2 -> /bin/bash: line 1: cd: too many arguments (rc=1)
$ cd "proj v2" -> rc=0, lands in "proj v2"
Quote it for the host the same way _version._render_argv renders its
copy-pasteable installer command: subprocess.list2cmdline on Windows,
shlex.quote elsewhere. Windows must use double quotes -- cd 'my project'
is a path-not-found in cmd.exe, while cd "my project" is accepted by
cmd.exe, PowerShell and Git Bash alike. Names needing no quoting are
returned unchanged, so the common case is byte-identical.
Shell-quote inner, Rich-escape outer.
Tests execute the printed command through a real shell rather than only
inspecting the string, and pin that an ordinary name stays unquoted.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(init): drop the now-redundant local escape imports that shadowed the module one
Rebasing onto main brought in three new extension-install helpers, and two
of them carry a function-local
from rich.markup import escape as _escape_markup
inside `register > init`. This PR adds the same import at module level, so
the locals made `_escape_markup` a local variable for the whole `init`
function — every use *before* those import lines then raised
UnboundLocalError: cannot access local variable '_escape_markup'
where it is not associated with a value
which broke `specify init` outright (7 of 8 tests in this file failed after
the rebase, all with exit_code 1).
The locals are redundant now that the module-level import exists, so remove
them. Verified with an AST scope walk that the only remaining
`_escape_markup` imports are the module-level one and the one inside
`_confirm_extension_url_trust`, which has no module-level use to shadow.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>