8f9dfa159e
* fix(backends): rewrite quoted virtual paths containing whitespace
The `convert_virtual_paths_in_command` regex
`(?<=\s)/[^\s;|&<>'"`]*` stopped at the first whitespace or quote,
so:
- `python "/skills/my skill/main.py"` was left completely
unchanged (the `(?<=\s)` lookbehind failed after the opening
`"`), and the shell then broke the inner unquoted path at the
embedded space.
- `python /skills/my skill/main.py` was truncated to
`python ./skills/my skill/main.py` (only `/skills/my` rewritten).
Replace the regex with `shlex.shlex(command, posix=True,
punctuation_chars=";|&<>")` so quoted regions stay whole, then
splice the rewrite back into the original command — extending the
splice span to include any matching quote chars around the path so
the fresh `shlex.quote` of the replacement isn't double-wrapped.
`_resolve_virtual_mount_path` now returns the unquoted path; the
caller owns shell-quoting, which avoids the previous
`shlex.quote` inside original `"…"` leaving literal `'` chars in
the argument value.
Unquoted paths with embedded whitespace remain a known limitation
(shlex has no way to know the user meant one path) — the
workaround of avoiding spaces in skill directory names still
applies, as flagged in the original issue.
Closes #237
* fix(backends): backslash-escaped paths, multi-path per token, subshell paths
- Fix backslash-escape handling: use unescape before rewriting
- Fix re.search→re.finditer: all /-paths in a token are rewritten
- Keep ( ) and backticks inside word tokens so paths spanning
\ or wrapped in backticks are matched correctly
- Add _try_rewrite helper with URL detection and unescape logic
- Add 10 contract tests pinning the din0s review cases
* fix(backends): restore () and backtick as shell operators for validate_command
- Restore ( ) and backtick to the operator set in _shell_token_spans.
Removing them caused a security regression: commands like (sudo ls)
would not detect sudo as a blocked command because (sudo became one
word token. With operators restored, validate_command correctly
catches blocked commands inside subshells and command substitutions.
- Fix _value_span_to_raw_span: the 'quoted' flag from the tokenizer
means the token *contains* a quoted segment (not necessarily starts
with a quote). Replace raw[0] assumption with a forward scan for
the first quote char, consuming unquoted prefix chars 1:1.
- Update test_system_path_with_shell_expansion: paths are now
partially rewritten because () are operators. Test updated to
reflect this known limitation (security > path rewriting).
* fix(test): cross-platform compatibility for pre-existing Windows failures
- python3 -> python in execute() calls (python is on PATH in any activated venv)
- sleep 10 -> _sleep_cmd(10) cross-platform helper
- str().endswith() -> Path().parts assertions (backslash-safe on Windows)
- shlex.quote exact-match assertions -> 'in' assertions (Windows quotes paths differently)
- mkdir -p E2E test -> preprocessor boundary test
- Skip 3 E2E tests on Windows: shlex.quote produces POSIX quoting incompatible with cmd.exe
141 passed, 3 skipped on Windows.
* fix: update docstring + strengthen shell-expansion test assertion
- Fix _value_span_to_raw_span docstring: no longer assumes raw[0] is
the opening quote, scans forward for first quote char
- Strengthen test_system_path_with_shell_expansion: verify
./workspace/notes is rewritten, not just notes in result
* style: ruff format backends.py + test_backends.py
* refactor(backends): simplify quoted virtual path rewriting
Replace 500+ line shlex tokenizer with 12-line pre-process step. Match quoted args via regex, unescape, rewrite via _rewrite_quoted_path, substitute with shlex.quote. 133 passed, 3 skipped.
* fix: guard bare absolute paths from double-rewrite by post-process regex
On POSIX, shlex.quote returns bare paths (e.g. /tmp/memories/note.md).
The pre-process substitutes these into the command, then the post-process
regex re-matches and incorrectly rewrites them.
Fix: _guard_bare_absolute wraps bare /-paths in single quotes so the
post-process regex''s character class stops at the quote char.
* style: ruff format
* fix(backends): narrow pre-process to exclude system-prefixed paths
Only rewrite quoted paths that are NOT known system prefixes.
* fix: narrow quoted-path pre-process to virtual mounts only
Only rewrite quoted /... paths that resolve to actual virtual mounts (/skills/..., /memories/...) or workspace-prefixed system paths. Remove catch-all that incorrectly rewrote bare paths like echo /hi.
Addresses din0s review feedback on #269.
* docs: update docstring for narrower quoted-path rewrite scope