Repository navigation
repo_shape.py, setup-project.py, shape-doctor.py, README and the handbook: under Git Bash (MSYSTEM set) the lines that follow the host are quoted for sh, through one question, host_shell_dialect, so the doctor's lines, setup's family lines and family.py's NEXT, switch -c, add and cd lines paste whole in the shell that printed them, and README's Windows sentence says so - #821
Conversation
…e no bytecode under templates/, the launcher guard reads family.py's siblings child, the quoting-sentence inventory follows an aliased quoter, a suffixless tool and each tool's calling functions, and an S5778 pytest.raises builds its argument first, so each guard fails when what it names breaks (#665) Four guards held less than they said. - scripts/render-cli-reference.py: `child_env()` sets PYTHONDONTWRITEBYTECODE=1, so the template's `siblings.py` (`from bootstrap import ...` from its own directory) leaves no `bootstrap.cpython-312.pyc` under templates/family-root/scripts/ when the generator runs its `--help` children. No `--help` text changes, so docs/cli.md is not regenerated (`render-cli-reference.py --check` still says it is what the generator produces). tests/test_cli_reference.py holds the variable in the environment and holds that an interpreter started under it writes none. - tests/test_validator_child_encoding.py: `interpreter_runs` accepts `[sys.executable or PYTHON, ...]` as well as `[sys.executable, ...]`, and LAUNCHERS gains scripts/family.py with `inherited_child_environment`, so dropping `env=` from `family.py siblings`' child (#214) fails the guard. - tests/test_readme_windows_quoting_sentence.py: the inventory follows `from repo_shape import shell_line as joined` (and `joined = shell_line`), scans every file `git ls-files` lists that is a `.py` file or has a python shebang (the root `bootstrap`), pins the functions that call a host-dialect quoter for scripts/family.py and setup-project.py instead of the file (shape-doctor.py stays every line, as the sentence says), and exempts the bodies of `shell_quoted` and `shell_line` in scripts/repo_shape.py by name, not the whole file. No quoting changes: the Git Bash dialect is a separate issue. - tests/test_bump_leg_paste_lines.py: `leg_placement(leg)` is evaluated before the `with pytest.raises(LegNotAsFound)` in `test_pasting_the_status_line_reads_that_leg` (Sonar S5778), as the test above it does. Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…e stacked base of #711
…e stacked base of #711
…stacked base, landed there as 2000488
…`MSYSTEM` set) the lines that follow the host are quoted for `sh`, through one question, `host_shell_dialect`, so the doctor's lines, setup's family lines and `family.py`'s `NEXT`, `switch -c`, `add` and `cd` lines paste whole in the shell that printed them (#711) The tools that quote a printed line for the host chose PowerShell's dialect wherever `os.name` was `nt`, and it is `nt` under Git Bash too, which runs the Windows Python. So Git Bash was handed `'C:\Users\Jane O''Neill'`, which it reads as `C:\Users\Jane ONeill`, a bare `C:\work\Atlas`, which it reads as `C:workAtlas`, and `Copy-Item` and `$env:PYTHONPATH = ...;`, which it has no command for. Brett Heap chose option (a) of #532's Git Bash question at 2026-10-16T13:07:34Z. - scripts/repo_shape.py: `host_shell_dialect(name=None, environ=None)` answers "nt" (PowerShell) on a Windows host whose environment has no `MSYSTEM`, or an empty one, and "posix" (`sh`) everywhere else, Git Bash and every MSYS2 shell included. `shell_quoted` and `shell_line` ask it when they are given no `platform`; an explicit one keeps its meaning. Its docstring names the limit: a PowerShell started from inside Git Bash inherits `MSYSTEM` and is answered `sh`'s. - setup-project.py carries a copy, as it does of the quoting, because it runs before any checkout of the standard exists. `SHELL_BARE`, decided at import from `os.name`, is gone: `shell_word` asks the question at each call and takes the alphabet and the escape from one answer, which the old code read twice. Setup's family lines follow it (L2-29). - shape-doctor.py: `quote_arg`, `restore_from_head`, `validator_command`, `bump_leg_command`, `checkout_notes`, `submodule_update_next` and `copy_command` ask it, so Git Bash gets `sh`'s quoting, `cp` and `env PYTHONPATH=...`. `scaffold_command` keeps its entry point and interpreter on the machine (`python setup-project.py` on Windows, from either shell) and takes only its quoting from the question. The interpreter is `PYTHON`, the machine's, on every line. - scripts/family.py is unchanged: its host-dialect lines go through `shell_quoted` and `shell_line`, `_print_next_steps`'s `cd` included. Tests: tests/test_git_bash_dialect.py emulates PowerShell, Git Bash, an empty `MSYSTEM` and POSIX on any host (each module under test gets an `os` whose `name` is the host's; the real `os.name` is never touched) and reads each line back with `shlex.split` or `powershell_words`: the question's answers, setup's copy held to them, `shell_quoted` and `shell_word` held to one answer on every host, and the lines of setup, family.py and the doctor. #724's inventory in tests/test_readme_windows_quoting_sentence.py now fails a function that quotes for the host and reads `os.name` itself. tests/test_family_printed_quoting.py reads the host through the question, and tests/test_setup_project_py.py unsets `MSYSTEM` where it patched the gone `SHELL_BARE`. Stacked on #724, whose final head 949fd4d is merged here and which has landed as 2000488, merged here with main. Lands after #630, which rewrites the README and handbook sentence this changes: the new wording waits for it. The copied `scripts/repo_shape.py` changes, so it rides in v1.1.1's shape bump. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reviewer's GuideThis PR makes generated host-specific commands pasteable in Git Bash and other MSYS2 shells by centralizing shell-dialect detection around non-empty Sequence diagram for host shell dialect command generationsequenceDiagram
participant Caller
participant Tool as setup/doctor/family
participant Dialect as host_shell_dialect
participant Quoter as shell_quoted_or_shell_word
Caller->>Tool: Generate a command
Tool->>Dialect: host_shell_dialect()
Dialect-->>Tool: nt or posix
Tool->>Quoter: Quote command words using dialect
Quoter-->>Tool: PowerShell or sh spelling
Tool-->>Caller: Pasteable command line
alt Windows with non-empty MSYSTEM
Dialect-->>Tool: posix
Tool-->>Caller: sh quoting, cp, env PYTHONPATH
else Windows without MSYSTEM
Dialect-->>Tool: nt
Tool-->>Caller: PowerShell quoting, Copy-Item, $env:PYTHONPATH
end
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
(lane openRepoShape-2, 2026-10-17T12:58:21Z) Independent review of #821 (#711) at Reviewed SHOULD 1. Nothing stops a pinned script from importing
SHOULD 2. The new guard misses
SHOULD 3. Recompute
NIT 1. Two lines are not checked per host; their mutants survive.
NIT 2. NIT 3. NIT 4. NIT 5. NIT 6. The guard misses four more patterns; on synthetic sources each returns
The behaviour rows cover every site that exists today. Fix: name these in the docstring's stated limit. NIT 7. Two wording points in the docs draft:
Checked and true
Could not verify
Lane 2 is taking the three SHOULDs and the NITs in fix round 1 now. Lane: openreposhape-2 (openRepoShape-2) |
…stion guard counts the doctor's line builders as quoters, and the Git Bash rows read back a kept repository's line and the lane trailer, so the mutants rv711 found survive no longer (#711) Fix round 1 on #821, from rv711's review of 8d074f1 (three SHOULDs, NITs 1-6; no MUST). - SHOULD 1. tests/test_family_printed_quoting.py strips `host_shell_dialect` from the older library too (`ADDED_BY_711`), so siblings.py is run beside a copy no pre-#711 holder could have. tests/test_pinned_copy_text.py's skew test reads every pinned script that imports from `repo_shape` (adding siblings.py, both bootstrap.py and validate-manifest.py) and refuses `host_shell_dialect` as it refuses `shown_path`. - SHOULD 2. tests/test_readme_windows_quoting_sentence.py's guard finds, per file, every function that takes a `platform` and hands it to a host-dialect quoter or to another such function (the doctor's nine line builders), counts calls to them as it counts the four quoters, and names a quoter's body by `Class.method`. So a new doctor row's `copy_command(s, d, os.name)` fails it, and so does a quoter that is a method. Its docstring names what it still does not see: a `sys.platform` branch, `from os import name`, a module constant built from `os.name`, a builder imported from another file. - NIT 1. tests/test_git_bash_dialect.py's `checkout_notes` row passes a kept repository, so `kept_branch_line` is read back per host. A lane name cannot hold an apostrophe (`LANE_NAME_RE`), so every real lane trailer reads alike in both dialects; a new test hands the trailer's quoter one through a stand-in `lane_trailer_argument`, so a trailer quoted for a fixed shell fails. - NITs 2-5, comments only: setup-project.py names the test that holds its `SHELL_BARE_NT` to repo_shape's on every host; tests/test_shape_doctor.py says `quote_arg(value, None)` asks `host_shell_dialect`; tests/test_git_bash_dialect.py says no CI job runs these lines under a real Git Bash; scripts/repo_shape.py's docstring no longer says siblings.py uses `shell_quoted`. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
(lane openRepoShape-2, 2026-10-17T13:23:43Z) Re-verification of #821 at Fix round 1 is one commit on SHOULD 1 (nothing stopped a pinned script importing
SHOULD 2 (the guard missed
SHOULD 3 (docs plan): FIXED. Every number in
NIT 1 (two lines not checked per host): FIXED
NITs 2-7: FIXED.
New, optional NIT. Merges.
Runs:
Could not verify. As in round 0:
Lane: openreposhape-2 (openRepoShape-2) |
…alue and pins each function's host-dialect calls, and the launcher guard reads `run_child` and `run([PYTHON, ...])`, with rows for `cli.py` and `setup-project.py` (#759) (#808) **Based on main.** #724 landed as `20004887`; this branch holds it and was restacked onto main as `84ce67c9`. **Head `08a6ed39`.** The branch holds #759's commit `052410b6`, two merges that took #724's final head and main, and one fix round (`08a6ed39`, lane 2's rv808). Tests only: `tests/test_readme_windows_quoting_sentence.py` and `tests/test_validator_child_encoding.py`. No shipped file changes, and no pinned copy. README, the handbook, `AGENTS.md` and `docs/cli.md` are untouched, and `render-cli-reference.py --check` passes. One commit, tests only: `tests/test_validator_child_encoding.py` and `tests/test_readme_windows_quoting_sentence.py`. No shipped file changes, so no pinned copy, README, handbook, `AGENTS.md` or `docs/cli.md` text moves. Pushed to `fix/759-quoting-launcher-guards` as a fast-forward of `84ce67c9`, the coordinator's restack onto main (which answers MUST 1, the conflict with main). Nothing was merged, rebased or amended. ### SHOULD 1: the `setup-project.py` row now flips on the fix it names, and the finding says six children **What failed, measured.** On `84ce67c9`, with the real `setup-project.py` changed in a scratch clone, one change per row. The row is `test_the_tools_that_run_the_projects_validators_give_them_an_environment[setup-project.py]`: `xfailed` means it is still red, `FAILED (XPASS strict)` means it says the finding is fixed. Log: `v116/f808/toolmut/`. | change to `setup-project.py` | `84ce67c9`'s guard | the new guard | |---|---|---| | none (control) | xfailed | xfailed | | `env=child_environment()` in `run`'s own `subprocess.run`, which reaches all six children | **xfailed: no signal, and `NOT_YET`'s reason stays false** | FAILED (XPASS strict): the row comes out | | an `env` parameter on `run`, passed at all six calls | FAILED (XPASS strict) | FAILED (XPASS strict) | | the same, but the call in `why_the_plan_did_not_go_through` left out | **FAILED (XPASS strict), with a child still bare** | xfailed | | `env=` at the five calls the old guard read, no parameter | **FAILED (XPASS strict), with a child still bare** | xfailed | | `env=` at four of the calls | xfailed | xfailed | | a seventh bare child added | xfailed | xfailed, and `test_setup_project_starts_six_children_and_the_guard_reads_each` fails | Lane 2's two mutants are the bold rows. The first is (a): the guard read the call sites and never `run`. The second is (b): there are six Python children, not five. `why_the_plan_did_not_go_through` runs the plan again, `again = run(plan, cwd=shape_root, capture=True)`, and `plan` is a parameter, so the old `argv_of` could not read the list. **The sixth child, measured.** I called the real `why_the_plan_did_not_go_through` with a plan that prints a `project ...` line holding an ellipsis and exits 2, under three encodings (`v116/f808/probe6.txt`): | `PYTHONIOENCODING` | what the second run's captured stdout holds | the words returned | |---|---|---| | `utf-8:strict` | `project Sample ` and U+2026 | `the plan was refused (see above).` | | `cp1252:strict` | `project Sample ` and U+FFFD | the same | | `cp1252:surrogateescape` | `project Sample ` and U+FFFD | the same | So the first five children write into the stream this process reconfigured to UTF-8, which is the two-encodings finding above. The sixth is captured and read as UTF-8 whatever it wrote: a cp1252 byte becomes U+FFFD. Nothing shows today, because the only test on those lines is `startswith(PLAN_STARTS)` and that prefix is ASCII. It is the same defect class, and the fix has to reach it too. **The fix** (`tests/test_validator_child_encoding.py`): - `interpreter_runs` reads a list a caller hands a function as a parameter, one level up, and a name bound in each branch of an `if`. A starter's own parameter is left to its callers, which are the runs, so `run`'s own `subprocess.run` is not counted as a seventh. On the shipped files this changes exactly two reads (`v116/f808/probe/survey.py`): `setup-project.py` gains the sixth child (`:2312` on `84ce67c9`), and `scripts/render-cli-reference.py` gains `:403` (NIT 1). - `environment_of` reads, where a call hands no `env=`, the `env=` of the function it calls when this module defines that function (`run`). A name is read as the LAST thing its function bound it to above the call. - `NOT_YET` names the six children, says five write into the reconfigured stdout and the sixth is captured, and names both fixes the row reads: an `env=` in `run`'s own `subprocess.run`, or an `env` parameter that all six calls pass. - `test_the_row_flips_on_the_change_the_finding_names` runs seven changes to a model of `setup-project.py` (the table's rows, on a source string). `test_setup_project_starts_six_children_and_the_guard_reads_each` pins the six functions of the real file: `check_names`, `clone_and_bootstrap`, `plan_and_confirm`, `scaffold`, `self_bootstrap` and `why_the_plan_did_not_go_through`. While the row is an `xfail`, a seventh bare child changes nothing in it, so the count is what would show it. ### NITs 1 to 6: which are taken, which stay | NIT | outcome | |---|---| | **1** `argv_of` read only the first list | **Taken.** Any list the name is bound to above the call counts; the first-or-last choice is gone. `SPELLINGS` has the name bound to two lists in each order (the `render-cli-reference.py` shape), and `NOT_RUNS` has a name bound to the interpreter only after the call. | | **2** three `host_values` behaviours without a case | **Taken.** `VALUE_USES` gains `self.quote = shell_line`, `a = b = shell_quoted` and a name read in an annotation; the value use in a method sits beside `test_a_method_is_named_with_its_class`. | | **3** `environment_of` joined every binding | **Taken.** The last binding above the call is read, with cases for a later rebinding, a rebinding after a branch, and one after the call. | | **4** spellings the launcher guard does not read | **Taken in part.** `interpreter_runs`' docstring lists them, as #821's guard lists "WHAT IT DOES NOT SEE", and `SPELLINGS` gains `subprocess.call`, `check_call` and `check_output`. **Stays unread:** a tuple argv, `[sys.executable, ...] + rest`, a function-local `exe = sys.executable`, `import subprocess as sp` and `from subprocess import Popen`, `os.exec*`. None is in shipped code, and each needs a resolver of its own. | | **5** aliases counted at the alias | **Taken in part.** `alias_of` follows `q: object = shell_quoted` as it follows `q = shell_quoted`, so `q(x) + q(y)` counts 2 and one switched to `'posix'` counts 1. `names_posix` reads a `'posix'` argument only after the value the quoter would quote, so `functools.partial(quote_arg, 'posix')`, which binds the value, is a host use. **Stays unfollowed:** an alias made by a tuple, a chained assignment, a walrus or a conditional. `host_calls`' docstring says so, and `NOT_FOLLOWED` pins what is counted instead (the statement, once for each quoter it names). None is in shipped code. | | **6** #821's `calls_to` reads `host_call` only | **Not changed here, and not #821.** See below. | **NIT 6, for whichever of #808 and #821 lands second.** #821's `calls_to` counts `host_call`'s calls and not `host_values`' uses, so on the merge `skips_the_question` does not see a quoter handed on as a value. Measured on a scratch merge of `08a6ed39` and #821's head `7581ffb9` (`v116/f808/nit6.txt`): | source | `skips_the_question("scripts/x.py", ...)` as merged | with the fix below | |---|---|---| | `' '.join(map(shell_quoted, w)) if os.name == 'nt' else ''` in `def a(w)` | `set()` | `{'a'}` | | `' '.join(shell_quoted(x) for x in w) if os.name == 'nt' else ''` in `def a(w)` | `{'a'}` | `{'a'}` | The fix is one expression, which the second lander adds to `calls_to`'s return: `| {function for function, _ in host_values(tree, names)}`. `host_values` names functions as `calls_to` does (`<module>`, `Class.method`). With it, `tests/test_readme_windows_quoting_sentence.py` on the scratch merge gives 43 passed (`v116/f808/nit6-run.log`). The merge itself is clean and needs no other change. ### Tests On `08a6ed39` (`dirty=0`), through `run9.sh` (gh stub first on PATH, bogus token, empty `GH_CONFIG_DIR`), `v116/f808/final-2.log`: `tests/test_validator_child_encoding.py tests/test_readme_windows_quoting_sentence.py tests/test_repo_hygiene.py tests/test_agents_md_codes.py tests/test_cli_reference.py`: **297 passed, 1 xfailed in 59.36s.** Per file: `test_validator_child_encoding.py` 82 to 111 cases (29 new), `test_readme_windows_quoting_sentence.py` 28 to 40 (12 new), the other three 147 together. On `84ce67c9` the two files gave 109 passed, 1 xfailed (`v116/f808/base-head.log`). No other test file imports or drives either one: `git grep` finds no use of `interpreter_runs`, `environment_of`, `LAUNCHERS`, `host_calls`, `host_quoting` or `quoter_names` outside these two files. On the scratch merge with #821 (`4386e5fe`, not pushed), these two files and `test_git_bash_dialect.py`, `test_family_printed_quoting.py` and `test_pinned_copy_text.py` give 417 passed, 1 xfailed (`v116/f808/merge821.log`). ### The new tests on `84ce67c9` I put the new test files over `84ce67c9` with its helpers back in place of the new ones (`v116/f808/mkbase.py`; a helper that is new is dropped, so a test that uses it fails with a `NameError`): **19 of the 41 new tests fail, 131 pass, 1 xfails** (`v116/f808/basefail-3.log`). The failures: 7 changes to the model of `setup-project.py` and the six-children pin; 3 `SPELLINGS` (a list a caller hands a parameter, by position and by keyword, and a name bound to two lists with the interpreter second); 5 `ENVIRONMENTS` (the last binding, three ways, and the environment `run` hands, two ways); and in the quoting file the `partial(quote_arg, 'posix')` use, the annotated alias statement, and the annotated alias counted at its calls. The other 22 pass there, because the base code already did what they say. They close mutants that survive `84ce67c9`'s own tests, measured below (six of them: NIT 2's three and NIT 4's three), or pin a limit the new code names. ### Mutants One mutant per new hunk, each a text change to the head's test files (`v116/f808/mutate.py`, `v116/f808/mut/results.jsonl`). `failed` counts the tests that fail, among the launcher guard's own tests for a mutant of `tests/test_validator_child_encoding.py` and in the whole file for one of `tests/test_readme_windows_quoting_sentence.py`. All 32 are caught. | hunk | mutant | failed | |---|---|---| | `bound_values`, above the call | the filter dropped / line number only / no sort | 2 / 1 / 1 | | `environment_of`, last binding | the first binding / every binding joined (the old code) | 2 / 2 | | `argument_for` | positional dropped / keyword dropped / always the first argument | 9 / 1 / 1 | | `lists_of`, callers | not read / callers of callers read / a starter's own parameter resolved | 10 / 1 / 15 | | `lists_of`, held lists | first only / last only | 1 / 1 | | `interpreter_runs`, any list | first only / last only | 1 / 1 | | `own_environment_of` | returns nothing / the call's own `env=` loses to it / recurses into a starter that calls itself | 3 / 4 / 1 | | `NOT_YET` row | unmarked / made to pass (`LAUNCHERS` value `""`, the strict `xfail` fires) | 1 / 1 | | `alias_of` | no annotated assignment / chained assignment read / any target kind read | 2 / 1 / 1 | | `names_posix` | every argument read / one slot after the quoter / a keyword quoter reads none | 1 / 1 / 1 | | `host_values`, alias statement | any child of one exempt / the statement a use | 1 / 5 | | `function_of` | `ClassDef` dropped | 1 | | `STARTERS` | `subprocess.call` / `check_call` / `check_output` dropped | 1 / 1 / 1 | Six of these survive `84ce67c9`'s tests, run on its own files with the same text change: `STARTERS` (three; 28 passed, 1 xfailed in the launcher guard's tests) and, in the quoting file (28 passed each), `host_values`' target-count check (`a = b = shell_quoted`), its target-kind check (`self.quote = shell_line`) and `function_of`'s `ClassDef`. On the new files each is caught, as the table says. ### Merge checks (`git merge-tree --write-tree HEAD <ref>`, main at `64ec47c3`) - **origin/main:** clean. - **#821 (`fix/711-git-bash-dialect`, `7581ffb9`):** clean, and the only other head that changes `tests/test_readme_windows_quoting_sentence.py`. This branch alone changes `tests/test_validator_child_encoding.py`. - **Every `fix/*` and `l3-*` head on origin (59, this branch's own among them):** 55 clean. Four conflict: `fix/758-git-command-scope`, `fix/762-validate-family-remedies-texts`, `l3-677-pins-hand-step-wip` and `l3-678-naming-checkout-wip`. Each conflicts the same way with origin/main and with `84ce67c9`, in files this change does not touch. ### Checklist candidates None new. NIT 6 is the one follow-up, and it belongs to whichever of #808 and #821 lands second. **1. The inventory sees a quoter handed on as a value, and counts calls.** `tests/test_readme_windows_quoting_sentence.py`: - `host_calls` also returns a host quoter that is read and not called: an argument or keyword of another call (`map(shell_quoted, words)`, `functools.partial(shell_line)`, `key=shell_quoted`), a list entry, a default or a return value. It is named under its own function (`host_quoting` keys by function), in source order. - A call told `'posix'` is `sh`'s, as for a direct call: `functools.partial(shell_line, platform="posix")` is not a use. - `NAMED` maps each function to its number of host-dialect uses (`_add_command` 3, `_print_next_steps` 2, `family_next_command` 2, the other three 1), and the per-function pin compares the counts. One of `_add_command`'s three calls switched to `'posix'` is 3 to 2 and fails, where the set of names it compared before still held `_add_command`. The failure prints the counts it found next to the table's. **2. The launcher guard reads two more spellings and has two more rows.** `tests/test_validator_child_encoding.py`: - `interpreter_runs` reads a call to `subprocess.run` and its kin, to the package's `run_child`, and to `run` (`setup-project.py`'s wrapper), whose argv is `[sys.executable, ...]`, `[sys.executable or PYTHON, ...]`, or a list starting with a name the module binds to `sys.executable` (`PYTHON = sys.executable`). The list may be spelled out, held in a local name (`plan = [PYTHON, ...]`, once or in each branch of an `if`), or handed to the calling function as a parameter by a caller in the module. A function that is itself a starter (`run`) is not a run; its callers are. A `PYTHON` imported from `repo_shape` is `python3` as a person types it, and is not read as the interpreter. - `environment_of` reads an `env=` keyword, or `run_child`'s second argument, through a local name (`cli.py`'s `scaffold` builds `env` and adds a key before it passes it on). Where the call hands none, it reads the `env=` of the function it calls when the module defines that function (`run`). A local name is read as its LAST binding above the call. - `LAUNCHERS` gets `src/openreposhape/cli.py`: its four `run_child` calls (`:436`, `:462`, `:474`, `:564`) each hand `standard.child_environment()`, which is what the file's docstring promises ("EVERY CHILD GETS `standard.child_environment()`"). That is the package environment's PATH and not an encoding, so it is the helper this row is held to. The row passes. - `LAUNCHERS` gets `setup-project.py`: its six Python children hand no environment, since `run` has no parameter for one: five `run([PYTHON, ...])` calls (in `self_bootstrap`, `check_names`, `plan_and_confirm`, `scaffold` and `clone_and_bootstrap`) and the sixth, in `why_the_plan_did_not_go_through`, which runs the plan again with `plan` a parameter. That row fails, and it is the finding below. - A separate test says each row finds at least one run. That keeps a spelling the guard cannot read from passing as "nothing to check". **Measured, not read.** `PYTHONIOENCODING=cp1252:strict` and `cp1252:surrogateescape` are what the other encoding tests here emulate for Windows' ANSI code page on a stdout that is not the console. A local run, `setup-project.py --project Sample --yes --org demoorg --local-remote-dir <dir> --into <dir>`, with the `gh` stub first on PATH: | `PYTHONIOENCODING` | exit | lines of stdout | lines that are not valid UTF-8 | |---|---|---|---| | `cp1252:strict` | 0 | 129 | 9 (24, 31, 38, 45, 64, 79, 80, 100, 102) | | `cp1252:surrogateescape` | 0 | 129 | 9 (the same) | | `utf-8:strict` (control) | 0 | 129 | 0 | `setup-project.py` reconfigures its own stdout to UTF-8 (`main`, `:2790`), and five of its children inherit that stdout with an environment of their own, so each writes in the code page its environment gives, and the sixth is captured and read as UTF-8 whatever it wrote (the table under "The sixth child, measured"). The lines are the naming validator's `family ...` rows with their em dash (3 lines), the scaffold's `shape ...` and `spec`/`code` lines with their ellipsis (4), and the validators `scripts/bootstrap.py` runs (2). Nothing raises, because `scaffold-project.py` and `scripts/bootstrap.py` escape what their stdout cannot hold. The stream is just two encodings. Log: `v115/w759/probe5/summary.txt`. **What the test does about it.** The row cannot pass without a change to `setup-project.py`, and this issue changes no tool. It is a strict `xfail` (`NOT_YET`): it passes while the row fails, and it fails the day the row passes, so whoever gives `run` an environment has to take the row out of the table. The mark names `AssertionError`, so any other error in the guard still fails. The fix is an environment from a copy of `inherited_child_environment` (reading `sys.stdout.encoding` after the reconfigure, so `utf-8:replace`), given either as an `env=` in `run`'s own `subprocess.run` or as an `env` parameter that all six calls pass. The row flips on either, and a change that leaves any one of the six bare leaves it red. `setup-project.py` cannot import the helper: it runs before there is a checkout to import from. - **The use rule is wider than the issue's wording, by one case.** The issue says "an argument of any call, or the value of an assignment the alias walk already follows". The rule here is: any read of a host quoter that is not the `func` of a call and not the alias statement itself (`joined = shell_line`, the assignment `quoter_names` follows). That covers the issue's two forms and also a list entry, a default, a return value and `self.quote = shell_line` (an assignment the walk does not follow). It differs on one point only: an alias that is assigned and never read. The alias statement is not counted, because every use of `joined` is, and counting it would make a module-level alias a `<module>` row that `NAMED` would have to name. - **A call that takes the quoter as an argument and is told `'posix'` is not a use.** `host_values` has its own small `names_posix` rather than reusing `host_call`'s inline test. `host_call` is left as it is, since #711 (the Git Bash dialect) edits the rule that decides what forces `sh`. - **The new cases sit in the middle of the file and `NAMED` changes only in its values.** #711 changes `tests/test_readme_windows_quoting_sentence.py` too, so these hunks are additive, and none of them is at the end of the file. - **The callee set is `subprocess.run/Popen/call/check_call/check_output`, `run` and `run_child`.** A call that only names the interpreter in a list (`shlex.join([sys.executable, ...])`) is not a run. Another spelling is a new entry in `STARTERS`, and `test_every_spelling_of_a_child_of_the_interpreter_is_a_run` is where it is shown. **On the shipped tools nothing changes.** The new `host_calls` returns what the old one did on every file `git ls-files` lists (`host_quoting` compared across both: identical), so no host-dialect line escaped the sentence before, as the issue says. `NAMED`'s counts match today's code: `_add_command` 3, `_print_next_steps` 2, `_switch_command`, `_print_next` and `_checked_mount_path` 1, `family_next_command` 2. **The tool mutants,** each run against `151a0861`'s own two test files and then against these. Logs under `v115/w759/mut/`. | Mutant | base tests | new tests | |---|---|---| | M1: `def _w759_mutant(words): return list(map(shell_quoted, words))` added to `update-shape.py` | 9 passed (survives) | `test_the_tools_that_quote_for_the_host_are_the_ones_the_sentence_names` fails: `update-shape.py` is among the files | | M2: `shell_quoted(org, 'posix')` in `_add_command` (one of three) | 9 passed (survives) | `test_the_functions_that_quote_for_the_host_...[scripts/family.py]` fails: `_add_command` 2, the table 3 | | M3a-d: `env` dropped from one of `cli.py`'s four `run_child` calls (`:436`, `:462`, `:474`, `:564`), one mutant each | 3 passed (survives) | the `[src/openreposhape/cli.py]` row fails, naming the line: ``src/openreposhape/cli.py:474 runs the interpreter without `env=standard.child_environment()` `` | | M3e: `env = dict(os.environ)` for `scaffold`'s `env` | 3 passed (survives) | the same row fails | **Against the base's helpers.** The new cases appended to `151a0861`'s files, without the new helpers: 12 of 19 fail in the quoting file and 12 of 19 in the launcher file (a `NameError` where the helper is new, an assertion where it is not). The 14 that pass are the negative cases (what is not a use, what is not a run) and the two `subprocess.run` spellings the guard already read. The `cli.py` and `setup-project.py` rows have no base row to run. Logs `v115/w759/basefail-quote.log` and `basefail-enc.log`. **One mutant per new hunk,** applied to the new test files only, each killed (`failed` counts the tests that fail): | Hunk | Mutant | failed | |---|---|---| | `host_calls` adds `host_values` | the value uses left out | 11 of 28 | | `host_values`, `names_posix` call | a call told `'posix'` still a use | 4 | | `host_values`, keyword | `ast.keyword` not climbed | 1 | | `host_values`, alias statement | the alias statement a use | 3 | | `names_posix` keyword | `platform=`/`posix=` ignored | 3 | | `names_posix` argument | a `'posix'` argument ignored | 1 | | `function_of` | every use named `<module>` | 11 | | source order | no sort by line | 1 | | `host_use_counts` | a count of 1 for every function | 3 | | `STARTERS` | `run_child` left out | 6 | | `STARTERS` | `run` left out | 6 | | `interpreter_names` | a name bound to `sys.executable` not followed | 6 | | `argv_of` (now `lists_of` and `argv_lists`; the fix round's own mutants table covers them) | a list held in a name not resolved | 1 | | `environment_of`, `run_child` | its second argument ignored | 4 | | `environment_of`, local name | a name not read as what it was bound to | 2 | | `NOT_YET` | the `xfail` mark dropped (the row is red) | 1 | | `NOT_YET` | the row made to pass (`LAUNCHERS` value `""`): the strict `xfail` fails | 1 | **These counts are `052410b6`'s.** At `08a6ed39` they are the fix round's: 297 passed, 1 xfailed over the same five files; the encoding file grows from 82 to 111 cases and the quoting file from 28 to 40. On `052410b6`, `run9.sh` (gh stub first on PATH, `GH_TOKEN` bogus, empty `GH_CONFIG_DIR`; `v115/w759/final-2.log`): `tests/test_validator_child_encoding.py tests/test_readme_windows_quoting_sentence.py tests/test_repo_hygiene.py tests/test_agents_md_codes.py tests/test_cli_reference.py`: **256 passed, 1 xfailed in 63.09s.** Per file: `test_readme_windows_quoting_sentence.py` 9 to 28 cases (19 new), `test_validator_child_encoding.py` 56 to 82 (26 new, one of them the `xfail` row), the other three 147 together. No other test file imports or drives what changed: `git grep` finds no use of `interpreter_runs`, `LAUNCHERS`, `host_calls` or `host_quoting` outside these two files. - **origin/main `64ec47c3`:** clean. **#724** landed as `20004887`, and nothing is stacked. The fix round's merge checks above are at `08a6ed39`; the rest of this section is `052410b6`'s. - **Every `fix/*` and `l3-*` head on origin (47):** clean, and none other than #724 touches the two test files. The branches that touch a tool the guards read (`scripts/family.py`: `fix/646`, `fix/684`, `fix/685`, `fix/686`, `fix/688`, `fix/689`, `fix/692`, `fix/693`; `update-shape.py`: `fix/674`; `setup-project.py`: `fix/696`, `fix/729`) are clean too. - **The wave-7 branches' committed tips** (`fix/757` to `fix/777`; `fix/760` is on origin): clean. - **Lane 2's #711 (the Git Bash dialect):** no branch on origin at 10:52Z (`git ls-remote origin 'refs/heads/*711*' 'refs/pull/711/head'` is empty), so it is not checked. It changes `NAMED` and this file; whichever lands second re-gates against the other. - Three older local branches (`fix/bootstrap-fast-forwards-tracking-branch`, `fix/scaffold-mount-ignored-legs`, `fix/scaffold-reuse-seeded-legs`) conflict, in files this does not touch, exactly as they do with origin/main. - **No change to a tool.** The `setup-project.py` row is the finding, held as a strict `xfail`; fixing `run` is a separate change. - **Dynamic access is not seen.** `getattr(repo_shape, "shell_line")` and `globals()["shell_line"]` hand a quoter on by name at run time. The inventory reads names in the source. - **The guard still reads only the tools in `LAUNCHERS`.** A survey with the new `interpreter_runs` over every shipped file found five more: `shape-doctor.py:1697` (sets its own UTF-8, as the table's comment says), `src/openreposhape/standard.py:560` (a digest program with captured output), `scripts/render-cli-reference.py:403` (passes `env=child_env()`, found once NIT 1 was fixed), `templates/assembly-root/scripts/bootstrap.py:148` and `templates/family-root/scripts/siblings.py:567` (neither passes an environment; see below). - **`templates/assembly-root/scripts/bootstrap.py`'s `_run` (`:148`) starts the validators with no environment, so a manifest name the code page cannot hold ends bootstrap.** Measured on a scaffolded project (a local `setup-project.py` run, then `project.yaml` `name: "日本"`), `PYTHONIOENCODING=cp1252:strict python3 scripts/bootstrap.py`: `validate-manifest.py` raises `UnicodeEncodeError` at `print(f"manifest ok: {manifest.get('name')} ...")` (`:972`) and bootstrap ends `bootstrap FAILED: manifest (exit 1)`, exit 1. With `utf-8:strict`: `manifest ok: 日本 (sample), 3 legs`, exit 0. It is #254's defect in a pinned copy, so a fix is a shape bump, and `make bootstrap` and `setup-project.py` step (7) both run it. - **`setup-project.py`'s `run` gives its children no environment** (the finding above). If the coordinator files it, the `NOT_YET` entry names the fix and the measurement. - **`templates/family-root/scripts/siblings.py:567`** starts each member's own `scripts/bootstrap.py` with no environment. Its stdout and the child's agree (neither reconfigures), so it is the first candidate's class and not the second's, and it has no row because it is a pinned copy. Review: lane 2's independent review (issuecomment-6236457891) and its re-verification of 08a6ed3, READY (issuecomment-6237256937). CI (Brett Heap's ruling 27): H0 is the head 08a6ed3 itself, which passed tests, tests-git-bash and SonarCloud; main has touched none of its files since its base 40e678e; and the local gate passed on its predicted squash tree 817a2ff over main 2c6543e: the readme_windows_quoting_sentence, validator_child_encoding, repo_hygiene, agents_md_codes, cli_reference sets, 298 passed, 1 xfailed. Landed on Brett Heap's word of 2026-10-17T21:47:28Z, in lane 5's session (posted on #473 as issuecomment-6241758069): "merge lane 5's v1.1.1 PRs once reviewed and green". Closes #759 Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… that follow the host are quoted for the shell they are printed in, PowerShell or Git Bash, and names `init`'s `cd` line among `family.py`'s (#711) #711 quotes `shape-doctor.py`'s lines, setup's family lines and `family.py`'s NEXT, switch -c, add, cd and git rm -r --cached lines for the shell they are printed in: `sh`'s under Git Bash (`MSYSTEM` set), PowerShell's in PowerShell. README's Windows sentence and the handbook's Windows panel still said PowerShell's. The wording waited in docs-wording.md while #630 owned both files. #630 has landed (190133d) and is merged here with main b9b7069, and the plan is recomputed on it: the old text is byte-identical at README.md:158-161 and docs/handbook.html:737-741. - README.md: "Printed commands are quoted for a POSIX shell, so paste them into Git Bash" is kept word for word. The exceptions name `init`'s `cd` line too, and are "quoted for the shell they are printed in, PowerShell or Git Bash, on Windows." One line more. - docs/handbook.html: the same words in `panel-windows`, and the limit `host_shell_dialect`'s docstring names, which README leaves out: a shell started from inside Git Bash inherits `MSYSTEM`, so a PowerShell started there is handed Git Bash's quoting, and cmd.exe is handed PowerShell's. - The seven markers of README's § Starting a project in a new organisation move together, 45a3f886 -> 4c523ffe. The page's other sections that read it (masthead, partway, ci, legs, doublepin, park) were re-read: none says which shell a printed line is quoted for. - tests/readme-cap.d/711.txt: +1, the line this adds. README goes 1474 -> 1475 and the cap 1475 -> 1476 (1422 + 2 + 5 + 12 + 22 + 10 + 1 + 1 + 1), one over README as main has it since #738 cut a line. tests/test_readme_windows_quoting_sentence.py's SENTENCE matches the new sentence once, and the handbook's panel holds it once, so the test does not change. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… counts a quoter handed on as a value, as #808's inventory does, so `map(shell_quoted, w)` beside a branch on `os.name` is seen (#711) #808 (#759) landed first, as 677f9bf, so NIT 6 of lane 2's review of it falls to this pull request. #808's `host_calls` counts a host quoter handed on as a value (`host_values`: `map(shell_quoted, w)`, `key=shell_quoted`, a `functools.partial`). But #711's `calls_to`, which `line_builders` and `skips_the_question` read, counted `host_call`'s calls only. So `' '.join(map(shell_quoted, w)) if os.name == 'nt' else ''` in `def a(w)` skipped the question unseen, while the same line written `shell_quoted(x) for x in w` was caught. `calls_to` now adds the functions `host_values` names, which it names as `scopes` does. On the 25 shipped files nothing changes: none hands a quoter on as a value, and each file's line builders, uses and skips are the same before and after. The new test pins the value use, the call beside it, and a value use told 'posix', which is not one. With the `host_values` term dropped, it fails at the value use. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…785) landed Three files conflicted. Each is composed, with no change to either side's behaviour: - shape-doctor.py, the `repo_shape` import: both names are kept, `host_shell_dialect` (#711) and `POWERSHELL_SINGLE_QUOTES` (#792). - shape-doctor.py, `quote_arg`: #792's `text = str(value)` and its branch for a value that is not ASCII (`quoted_beyond_ascii`), with #711's dialect, `host_shell_dialect()` where no platform is given. So that value is `sh`'s `'...'` or `$'...'` off Windows and under Git Bash, and PowerShell's verbatim string or `('...' + [char]0x...)` in PowerShell and cmd.exe, as README now says. - README.md: docs-wording.md section 4, "#785 first, then #711": #711's sentence, then #785's, which says "Off Windows and in Git Bash", three lines where it was two. - docs/handbook.html, `panel-windows`: the same two sentences, and "a cmd.exe started anywhere else is handed PowerShell's" for "cmd.exe is handed PowerShell's", which read as a cmd.exe started inside Git Bash too, and that one inherits `MSYSTEM`. - docs/handbook.html, the seven markers of README's § Starting a project in a new organisation: neither side's blob (4c523ffe, 3d4a01ff) but the merged section's, e0d2b672, the blob docs-wording.md computed for this order. And tests/readme-cap.d/711.txt, which did not conflict, says +2: on top of main #711 now adds two lines, its own clause and the third line of #785's reworded sentence. README is 1478 lines; the cap is 1422 + 2 + 5 + 12 + 22 + 10 + 1 + 2 (785) + 1 (786) + 2 (711) = 1479. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MSYSTEM set) the lines that follow the host are quoted for sh, through one question, host_shell_dialect, so the doctor's lines, setup's family lines and family.py's NEXT, switch -c, add and cd lines paste whole in the shell that printed themMSYSTEM set) the lines that follow the host are quoted for sh, through one question, host_shell_dialect, so the doctor's lines, setup's family lines and family.py's NEXT, switch -c, add and cd lines paste whole in the shell that printed them, and README's Windows sentence says so
|
(lane openRepoShape-2, 2026-10-18T05:26:15Z) Review of #821 (#711)'s new commits ( This verdict is for head NIT 1. NIT 6's test checks only a value use inside a plain function (
NIT 2. No test prints a non-ASCII path under an emulated Git Bash or PowerShell host (
NIT 3 (optional). The handbook's cmd.exe clause (
A possible rewording: "so a PowerShell or cmd.exe started there is handed Git Bash's quoting. A cmd.exe started anywhere else is handed PowerShell's, which it does not read once a word is quoted, so paste those lines into PowerShell." NIT 4 (optional). One stale word in a docstring ( Checked and true
Could not verify
Lane: openreposhape-2 (openRepoShape-2) |
|
|
Sorry @brettheap, you've used your own review budget of 250,000 diff characters for the last 7 days. You can request another review in 5 days and 5 hours by commenting |
…light's offers and `--shape-ref` is held to git's rule part by part, so a rehearsal no longer writes git's global config and `a.lock/b` is refused before the clone (#857) (#873) Stacked on #811 (`fix/777-typed-name-edges`); retarget to main once it lands. #756 has landed as `4dea85ee`, and #811's head is now `b6f56cad`: #756's squash merged in, and a test-only change to `tests/test_setup_project_name_edges.py`. This branch stays on `050abea6`, the merge base of the two, so its diff against #811's branch is its two commits: the fix (`79a33f11`) and fix round 1 (`ac570069`), which changes two test files and nothing else. Lane 5 restacks it after #811 lands. **Direction: the silent reading.** A run passed `-- --dry-run` reports what is missing and asks nothing that would change the machine. Where a terminal would have been offered a command, it is told `not offered: --dry-run was passed through. A run without it offers this here, and so does --preflight.`, and asked nothing. The plan's last line now ends: ``The preflight made none of its offers; a run without --dry-run makes each one marked `not offered` above.`` `--preflight` still makes every offer, whatever follows `--`: it is the run that exists for them (#59), and it returns before anything reads the passthrough. **Why.** Measured at `050abea6`, what each offer does on a typed `yes`, and all three change the machine: - an install (`offer_missing`): git or gh, through the package manager; - `gh auth login` (`_offer_login`): gh's own sign-in; - `gh auth setup-git` (`_check_credential_helper`): gh writes a credential helper into the person's global git config. A pty run of `setup-project.py Sample --org demoorg --elected-by Tester --into <dir> -- --dry-run`, under a signed-in stub `gh` and a git with no helper, asked ``Run `gh auth setup-git` now? Type yes to continue:``. The typed `yes` ran it, and the stub, which writes what the real one writes, added `[credential "https://github.com"]` to the file `GIT_CONFIG_GLOBAL` names. The run then ended at the plan saying `nothing was created and nothing was cloned`. With gh signed out, the same run asked ``Run `gh auth login` now?``, and the `yes` signed the stub in. The help said a `--dry-run` there "ends this run at the plan, creating nothing". And #811's CI run 39027235147 showed five rehearsals that typed only the project name waiting at that question until they were killed. Nothing argued for the offering reading: the offers stay one flag away (`--preflight`), and the real run makes them. **What changed.** `preflight` passes `offering = not passes_a_dry_run(opts.passthrough)` to `preflight_checks`, and it goes down through `_check_git`, `_check_gh`, `offer_missing`, `offer_install`, `_offer_login` and `_check_credential_helper` to `offer`. `offer` tests it after `can_ask()`, so a run with no terminal prints what it printed before, and it is a bool, never `Options`, so `test_the_offer_helpers_cannot_see_the_yes_flag` still holds. The help says it, and `docs/cli.md` is regenerated. `AGENTS.md`, `README.md` and the handbook were grepped for `dry-run`, `creating nothing` and the offers: no sentence there is made false, so none is touched. The git install offer cannot be reached by a dry run end to end: `_main` refuses a machine with no git (`GIT_MISSING`) before the preflight runs. It is held in process, through `preflight`, as the other two are. **Tests** (`tests/test_setup_project_dry_run_offers.py`): - `test_a_dry_run_makes_none_of_the_offers_preflight_makes[install|login|helper]`, once for `--dry-run` and once for its short spelling `--dry` (`passes_a_dry_run` reads both), in process on every platform. A fake terminal types `yes` to anything and a recorder runs nothing. `preflight` on `Sample --org demoorg -- <flag>` makes no prompt and runs no offered command, says `not offered` once per offer (2, 1, 1), and refuses or passes as before. `preflight_only` on `--preflight -- <flag>` then makes the same 2, 1, 1 offers. - `test_a_dry_run_with_no_terminal_says_what_a_run_without_it_says[install|login|helper]`, in process: with no terminal (stdin is not one and there is none to open), `preflight` on `-- --dry-run` prints no `not offered` line, and what it prints is what the same run prints without `--dry-run`. It holds the order in `offer`, `can_ask()` before `offering`. - `test_a_dry_run_leaves_git_with_no_helper_as_it_found_it[py|piped|py with --dry]`, a pty run (`PTY_SKIP`). A signed-in stub `gh`, an empty system config and a global config of the run's own, with no `GIT_CONFIG_COUNT` and run from a folder that is no repository. The run asks nothing, exits 0 at the plan, prints the `[??]` warning with the `not offered` line under it and the new last line, never calls `gh auth setup-git`, and leaves the global config the bytes it was. - `test_a_dry_run_does_not_sign_gh_in`: the same with gh signed out (git has a helper, so the login is the one offer at stake). It asks nothing, the `[!!]` line has the `not offered` line under it, it refuses `one or more prerequisites are missing`, and gh is still signed out. **Direction: finish the rule,** in a new `why_not_a_ref` beside `why_refused`, which `checked_ref` asks after `REF_RE`. Git's rule is per part: no part is empty (`//`, a trailing `/`), none starts with `.`, none ends in `.lock`, and the whole holds no `..` and does not end in `.`. Each failure has its own sentence (`WHY_EMPTY_PART`, `WHY_DOT_PART`, `WHY_LOCK_PART`, `WHY_DOT_END`). `WHY_RANGE` and `WHY_LOCK` keep #811's words, so its `ref-range` and `ref-lock` cases are unchanged. `REF_RE`'s comment and `checked_ref`'s docstring say what is asked. **Evidence.** At `050abea6`, `checked_ref` beside `git check-ref-format --branch` disagreed on seven of the issue's ten names (`a.lock/b`, `x/y.lock/z`, `release.lock/v1`, `a/.b`, `a//b`, `a/b/`, `a.lock/`), and on `a.` and `a/b.`, under git 2.43.0 and 2.47.1 alike. Over all 3110 names of up to five parts from {`a`, `0`, `.`, `/`, `-`, `.lock`} that `REF_RE` takes, they disagreed on 1122 before and on none now, under both versions. In self-bootstrap mode `--shape-ref a.lock/b` cloned the standard and ended in `error: pathspec 'a.lock/b' did not match any file(s) known to git`, exit 1. Now it is refused before `(0) self-bootstrap` prints. **Tests** (`tests/test_setup_project_ref_parts.py`): - `test_the_tool_and_git_give_one_answer[<name>]`: the issue's ten names and fourteen around them (`a.`, `a/b.`, `a./b`, `a.lockb`, `a/b.lock/`, `a/.lock`, `a..b`, `refs/tags/v1.0`, `v1.0.0`, and five from fix round 1). Each is fed to `checked_ref` and to `git check-ref-format --branch`, which must give one answer, and the tool's reason is pinned. No network. Fix round 1's five are `a/.b/c` and `a/b/.c/d`, a part that starts with `.` that is not the last part; and three names that break two rules, which hold the order `why_not_a_ref` asks them in (its docstring says the sentence names the first one the value failed): `a//..b` is a range, not an empty part; `a//.b` an empty part, not a dot part; `a.lock/b.` a lock part, not a `.` at the end. With `a/.lock` and `a/b.lock`, that holds each of the five pairs of neighbours in the order. - `test_a_ref_git_refuses_is_refused_before_the_clone[a.lock/b|a/.b|a//b]`: a self-bootstrap run exits 2 with the reason, prints nothing on stdout and clones nothing. Through `run9s.sh`, `SHARDS=3`, at `79a33f11`: the two setup files, every other test file that names `setup-project` (`git grep -l 'setup-project' -- tests`), the two new files and the trio, 25 files. - git 2.43.0: 1606 passed, 3 skipped, 1 xfailed in 746.00s, rc 0 (shards 428 passed, 3 skipped, 1 xfailed; 546 passed; 632 passed) - git 2.47.1: 1606 passed, 3 skipped, 1 xfailed in 677.22s, rc 0 (shards 428 passed, 3 skipped, 1 xfailed; 546 passed; 632 passed) Before the commit, unsharded on git 2.43.0: `test_setup_project_py.py`, `test_setup_project_name_edges.py`, `test_setup_project_typed_name.py` and the two new files, 377 passed. `test_setup_sh.py` and the trio, 235 passed. Fix round 1, at `ac570069` (`setup-project.py` is unchanged, so every other file's result stands): - git 2.43.0, `SHARDS=3`: `test_setup_project_py.py`, `test_setup_project_name_edges.py`, `test_setup_project_typed_name.py`, the two new files and the trio, 535 passed in 174.38s, rc 0. - the two new files, 40 tests: 40 passed on git 2.43.0 (3.74s) and 40 passed on git 2.47.1 (2.64s). The two new files, run against `050abea6`'s code: at `ac570069`, 40 tests, 28 failed and 12 passed (at `79a33f11`, 28 tests: 20 failed, 8 passed). All ten dry-run cases that make an offer fail: the three machines in both spellings, the three terminal runs and the signed-out run. Line 2's fifteen table rows that `050abea6` gets wrong fail: ten names git refuses that it passed, `a/.lock`, which it refused for the `.lock` and not for the part that starts with `.`, and fix round 1's `a/.b/c`, `a/b/.c/d`, `a//.b` and `a.lock/b.`. So do the three self-bootstrap cases. The 12 that pass are the three no-terminal cases (`050abea6` made no offer there either), and nine table rows: eight where `050abea6` already agreed with git (`a.lock`, `a/b.lock`, `topic.locked`, `a./b`, `a.lockb`, `a..b`, `refs/tags/v1.0`, `v1.0.0`) and `a//..b`, which it refused for its `..`, with the sentence the order gives. They hold the table to both directions, `topic.locked` passing among them. 23 mutants, each run against its tests, and all 23 caught: - the gate in `offer` removed; - `offering` not passed on in each of `offer_install`, `offer_missing`, `_check_git`, `_check_gh` (install, login), `_offer_login`, `_check_credential_helper`, and each of `preflight_checks`'s three calls; - `preflight` always offering; - `preflight_checks` silencing `--preflight -- --dry-run` too; - the plan's new sentence removed; - the `not offered` words changed; - the help reverted (`tests/test_cli_reference.py`); - `checked_ref`'s old two checks put back (`text.endswith(".lock")`); - each of the four new parts of the rule dropped; - `why_not_a_ref`'s answer ignored; - the `.lock` part sentence changed. The comment and docstring hunks have no mutant: they are prose. None survived. An independent review then made 14 mutants of its own at `79a33f11`. Nine were caught. Five survived the tests as first pushed (the two new files and the setup tests): R1, the dry-run check put before `can_ask()` in `offer`; R3, the `..` and empty-part checks swapped; R4, `parts[-1].startswith(".")` only; R7, a literal `"--dry-run" not in opts.passthrough` in `preflight`; and R13, `parts[:2]` only. Fix round 1 closes each, rebuilt from the review's descriptions and run at `ac570069` against the two new files on git 2.43.0: - R1 fails the no-terminal test on all three machines (3); - R3 fails `a//..b` (1); - R4 fails `a/.b/c` and `a/b/.c/d` (2); - R7 fails the three `--dry` cases in process and the `--dry` terminal run (4); - R13 fails `a/b/.c/d` (1). Run against the two files as first pushed (`79a33f11`), the same five all survived, which is what the review found. Two more mutants for the new order rows are caught: `a//.b` fails with an empty part checked after a dot part, and `a.lock/b.` fails with a `.` at the end checked before a lock part. The other nine of the review's mutants were caught before and were not rerun. `git merge-tree --write-tree` of `79a33f11`, and of `ac570069` for the first line: - **#811's head `b6f56cad`: clean**, for both (`git merge-tree --write-tree b6f56ca ac57006`, tree `3e369754`). Fix round 1 changes only the two test files this PR added, so the other lines below stand for it: they were checked at `79a33f11` and not run again. - **origin/main `d7befb91`: conflicts in `setup-project.py` and `tests/test_setup_project_typed_name.py`.** The conflict is inherited: `050abea6` alone conflicts with `d7befb91` in the same two files (#756 landed as a squash, and #811's branch held its history). A commit-tree prediction of `79a33f11` merged onto `b6f56cad` is clean against `d7befb91`. - #859 `495d4b33` (`fix/838-github-name-limits`): clean. `setup-project.py` and `docs/cli.md` merge by themselves, with this head and with the predicted restack. - #821 `e15c237b` (`fix/711-git-bash-dialect`): clean, `setup-project.py` merging by itself. The predicted restack conflicts with it only in `README.md`, `docs/handbook.html` and `shape-doctor.py`, where `d7befb91` itself conflicts with `e15c237b`. None of the three is this branch's. - `fix/764` `05a1ebb3`, `fix/796` `2d51f181`, `fix/798` `70801c91` and `fix/803` `08f7a283` (`docs/cli.md`): clean. - No other `fix/*` or `l3-*` branch touches these four files. - #848's scan on main (`counts_set_to_a_number`) finds no count set in either new test file. - `setup.sh`'s shim keeps its own copy of the old rule (`*..*|*.lock`), and its comment says it mirrors `setup-project.py`. Under `cat setup.sh | bash -s -- --shape-ref a.lock/b` the shim clones the standard and ends in git's `pathspec 'a.lock/b' did not match`, exit 1, before this file's check runs. Measured at this head, it is outside #857's lines and is reported to lane 5 as a checklist candidate. - The plan's last line prints on every dry run, including where the preflight marked no offer `not offered`, where "each one marked `not offered` above" points at none. It is true as written. Printing it only where an offer was marked would take a count that `offer` writes and the plan reads, held in the module: `offer` is three to five calls below `preflight`, and each caller between returns a bool that the next one reads. That is state in a file whose `offer` takes its inputs as arguments and nothing else, for a sentence most dry runs would then not print, so it is left. - No Windows or macOS run: CI is Linux only (#597). The in-process test runs on every platform; the pty tests are POSIX only. Review: rv873, lane 5's independent review, READY at `79a33f11` with S1 (issuecomment-6245576863); f873's fix round 1 `ac570069` re-checked READY with no findings (issuecomment-6245715704). S2 is filed as #874. `0774ad60` restacks it onto #811's squash `bc362333` with three merges and the same `-U0` lines. CI (Brett Heap's ruling 27 and his fast-refresh ruling of 2026-10-15T22:48:35Z): H0 0774ad6 (the restack of the branch onto main after #811 landed) passed tests, tests-git-bash and SonarCloud; the head e41a38a is a clean update-branch merge of main 85aa8a1, with tests-git-bash and SonarCloud green; and the local gate passed on its predicted squash tree fa52066 over main 85aa8a1: the doctor_next_is_a_command, doctor_validator_rows, setup_project_dry_run_offers, setup_project_ref_parts, repo_hygiene, agents_md_codes, cli_reference sets, 320 passed. Landed on Brett Heap's word of 2026-10-17T21:47:28Z, in lane 5's session (posted on #473 as issuecomment-6241758069): "merge lane 5's v1.1.1 PRs once reviewed and green". Closes #857 Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>



Lane: openreposhape-2 (openRepoShape-2)
Claim: #711 (comment)
v1.1.1. A draft until the v1.1.0 tag. Stacked on #724, which has landed (below). #630, which rewrote the README and handbook around the sentence this changes, has landed too (
190133d5), so the docs wording is in this PR now. #792 (#785), which writes into the same paragraph and intoquote_arg, landed after that, and maind7befb91is merged here with it ("Merge round after #792 (#785)", below).Stacked on
.pycthe CLI reference's child writes under templates/, a launcher guard blind tofamily.py, a quoting-sentence inventory with three holes, and an S5778pytest.raises#665), branchfix/665-four-guards, which rewrotetests/test_readme_windows_quoting_sentence.py's inventory that this extends.151a0861dd6ce4a82cf78100fd23b86932c166a2merged with--no-ffas7fedbb06, on mainbf1f099b. No conflicts.949fd4dd4197de263900f9ea2feb410d0ef5adf8("Merge branch 'main' into fix/665-four-guards", mainbf1f099b; the same tree as7fedbb06), merged asa3e900c7.20004887, blob for blob equal to949fd4ddin all five files it touched. Main20004887is merged as2f2928dd, with no conflicts; that merge's tree is main's own.git merge-tree's:677f9bfaas0357e7f4. It holds README, AGENTS.md, docs/cli.md and the handbook:pipx install openreposhapeis the second way in and the first on native Windows, git-filter-repo is installed in install.sh's order and never with a barepip install, and a first draft of the v1.1.0 release notes (#563) #630 (190133d5) and tests/: the quoting-sentence inventory sees a quoter handed on as a value and pins each function's host-dialect calls, and the launcher guard readsrun_childandrun([PYTHON, ...]), with rows forcli.pyandsetup-project.py(#759) #808 (677f9bfa).5fc049b7as1a285512. It adds tests/test_doctor_legs_members_next.py and 24 other files: tests append what they tell git to theGIT_CONFIG_COUNTentries the environment holds, so a pasted line keeps the session's maintenance settings and the Windows job's two (#758) #818, shape-doctor.py: a conflicted shape pin's NEXT takes the side whose pin reads, and names no line where no side's pin reads, so run as printed it no longer leaves the doctor naming the same NEXT again (#752) #779, family.py:add,bumpandremoverefuse a cherry-pick or a revert stopped in a holder whose refs are a reftable, asking its ref store forCHERRY_PICK_HEADandREVERT_HEADby their exact names where no file holds them (#684) #846, siblings.py: a file, or a foldermake siblingscannot list or enter, at../<Project>is NOT A CLONE, reported and left as it is, so the run no longer ends in a traceback atiterdirbefore the members after it are placed, andmake parkno longer ends in one over a folder it cannot enter (#735) #772, README.md, the handbook, AGENTS.md and the family-root docs:init's--intosentence names thefamily-landing-in-standardrefusal,make siblingsis described as it stops before cloning, andmembers/has its one-spelling rule (#695) #738 (e62b5e17, README § Families), scripts/shape_materialize.py and adopt-project.py: a--pin-sourcethat is no clone's top level is refusedpin-source-not-a-clone, andcheckfinds a recorded one before a partial clone's NOTE, so a folder inside a clone no longer records the digest of part of its tree, or of nothing (#800) #842 and family.py, scaffold-project.py and bump-leg.py:init's plan rows, seven refusals and bump-leg's root name each path asshown_pathnames it, so an ESC or a line break in a holder, folder, lock, local remote or root no longer reaches the terminal from those plan rows or those refusals' details #814.b9b70692as7564e916. It adds validate.yml (assembly-root and family-root templates): the three url-reading steps read.gitmodulesby the rules the doctor andsiblings.pyread it by, so a section git ignores, a section with no path, apathorurlbeginning with-, an unmerged file or a file git refuses whole no longer puts a repository in the App token or the lists, and a refused file fails the step with its cause (#786) #790 (7b210593, a README line andtests/readme-cap.d/786.txt) and validate-family.py: the wrong-case move back mends.gitmodules, adds--sparseand moves an entry in the way aside, a row's staged removal and a url-less section are findings, and two refusals name a cure that works (#762) #820.d7befb91as4ed7d2e0, after shape-doctor.py: a path that is not ASCII is printed as that path in every NEXT, keep line and WHY, as itself where the report's encoding writes its bytes and escaped whole where it does not, so a line namingProjets/Équipeor a Latin-1 name runs as printed (#785) #792 (shape-doctor.py: every non-ASCII character in a path it prints becomes?, so a NEXT or keep line naming such a path (a membercafé, a holder underProjets/Équipe) cannot be pasted as printed #785) landed. It adds shape-doctor.py: a path that is not ASCII is printed as that path in every NEXT, keep line and WHY, as itself where the report's encoding writes its bytes and escaped whole where it does not, so a line namingProjets/Équipeor a Latin-1 name runs as printed (#785) #792 (5cbec222), validate-repository-naming.py, validate-manifest.py and validate-pins.py: alegs:that is not a list ismanifest-legsin every gate, a chain link or pin that is not a name is a finding, and the naming gate never reads stdin under--project(#714) #826, shape-doctor.py: a shape copy renamed on one side of a merge, conflicted under a name no pin has a row for, is named in a conflicted shape pin'swhyandleft_for_a_personas renamed from that copy, for a person to settle, and never taken (#753) #781, adopt-project.py:planasks whether it can write--outwith its flags, before any work, and refuses a folder, a pipe, a missing folder or one nobody can write in asadopt-out-unwritable, and saysplan written toonce the plan is on disk, so a mistyped--outno longer ends in a traceback after the whole plan is built (#799) #833, setup-project.py and the assembly root's bootstrap.py: every Python child is told the encoding its words are read in, so a cp1252 stdout gets one encoding from setup, andmake bootstrappasses a project whose name the code page cannot hold #851, setup-project.py: a project name typed at the prompt is checked as--projectis and an empty line says no name was typed, so../x,a/b,..and--helpare refused by name, not by an--idhint, a help page or a probe write (#729) #756 and adopt-project.py:checkprints the rename-first remedy asexecutedoes, and a plan's recordedsource: repository:holds its names where the live source names none, socheckandexecuteboth refuseplan-not-the-source(#716) #825. Three files conflicted, and each hunk is composed (below).2f2928dd:8d074f16, the change, and7581ffb9, fix round 1. After7564e916:5035128e, README and the handbook, ande15c237b, tests/: the quoting-sentence inventory sees a quoter handed on as a value and pins each function's host-dialect calls, and the launcher guard readsrun_childandrun([PYTHON, ...]), with rows forcli.pyandsetup-project.py(#759) #808's review NIT 6 (both below). Then the merge4ed7d2e0, whose resolution is the merge round below.What changed
shape-doctor.py's lines,setup-project.py's family lines and the host-quoted lines ofscripts/family.pychose PowerShell's dialect whereveros.namewasnt, andos.nameisntunder Git Bash too, because Git Bash runs the Windows Python. So a line printed in Git Bash was quoted for a shell the person was not in. Brett Heap chose option (a) of #532's Git Bash question at 2026-10-16T13:07:34Z:shquoting under Git Bash, keyed onMSYSTEM.scripts/repo_shape.py'shost_shell_dialect(name=None, environ=None). It answers"nt"(PowerShell) on a Windows host whose environment has noMSYSTEM, or an empty one, and"posix"(sh) everywhere else, Git Bash and every MSYS2 shell included.shell_quotedandshell_lineask it when they get noplatform; an explicitplatformkeeps its meaning. The docstring gives the rule, whyMSYSTEM, and the limit: a PowerShell started from inside Git Bash inheritsMSYSTEMand is answeredsh's, and nothing tries to detect it. It answers nothing about the machine:PYTHONstayspythonunder Git Bash.setup-project.pycarries a copy (host_shell_dialect, beside its copy of the quoting), because it runs before any checkout of the standard exists.SHELL_BAREis gone, and the decision moved to call time. It was decided at import fromos.name, while the escape askedos.nameat each call: two reads of one question.shell_wordnow asks the question once per call and takes both the alphabet (SHELL_BARE_NTorSHELL_BARE_POSIX) and the escape from that one answer, soMSYSTEMreaches it, whenever it is set. Setup's family lines (family_next_command, L2-29) follow it.shape-doctor.py.quote_arg,restore_from_head,validator_command,bump_leg_command,checkout_notes,submodule_update_nextandcopy_commandask the question (imported fromrepo_shape, asPYTHONis). Under Git Bash that givessh's quoting,cpand notCopy-Item, andenv PYTHONPATH=...and not$env:PYTHONPATH = ...;.scaffold_commandkeeps its entry point and interpreter on the machine (os.name): a Windows machine is handedpython setup-project.pyfrom PowerShell and from Git Bash alike, and only its quoting follows the question. The interpreter on every line is stillpython_command(platform),PYTHONwith no platform.scripts/family.pyis unchanged. Its host-dialect lines (_print_next,_switch_command,_add_command,_print_next_stepswith thecd {shell_quoted(work)}line lane 5 added to the issue,_checked_mount_path) callshell_quotedandshell_linewith no platform, so they follow the question.Measured by a probe that loads each tool in process and gives each module an
oswhosenameis the host's, withMSYSTEMset or unset. PowerShell and POSIX hosts print exactly what they printed before. Under Git Bash (nt,MSYSTEM=MINGW64), before and after:(
python3because the probe ran on Linux, wherePYTHONis decided at import; a Windows host printspython.bump_leg_command,submodule_update_next,quote_arg, setup'saddline andfamily.py'sswitch -candaddlines move the same way.) Git Bash reads thebeforeapostrophe asC:\Users\Jane ONeill, and a bareC:\work\AtlasasC:workAtlas.README and the handbook are in
5035128e(below). No--helptext changes, sodocs/cli.mdis not regenerated.README and the handbook (
5035128e)#630 owned README and the handbook until it landed, as
190133d5, so the wording waited indocs-wording.md, measured at #630's headb2a62ec7. It is recomputed here on mainb9b70692(the merge7564e916). Today's old text is byte-identical to the plan's "Old", at the same lines, and the section's old blob is the same45a3f886.README.md, "Windows, natively". The first sentence, "Printed commands are quoted for a POSIX shell, so paste them into Git Bash", is kept word for word. The second namesinit'scdline amongfamily.py's, and its tail changes.docs/handbook.html,panel-windows. The same words, and one sentence README leaves out: the limithost_shell_dialect's docstring names, plus cmd.exe. The widest new line is 100 columns; the panel already runs to 129.tests/test_readme_windows_quoting_sentence.py'sSENTENCEmatches the new README sentence once, and the panel holds it once, so the test does not change.The seven markers. The
§ Starting a project in a new organisationmarkers move together,45a3f886→4c523ffe, the blob the plan computed onb2a62ec7. They are at :475, :615, :1093, :1130, :1193, :1449 and :1748; before the panel's three new lines the last five were at :1090, :1127, :1190, :1446 and :1745. The other sections that read this README section (masthead,partway,ci,legs,doublepin,park) were re-read. None says which shell a printed line is quoted for, so only their markers move.The cap. README goes 1474 → 1475. On main
b9b70692the cap is 1422 + 2 (460.txt) + 5 (550.txt) + 12 (563.txt) + 22 (564.txt) + 10 (606.txt) + 1 (683.txt) + 1 (786.txt) = 1475.tests/readme-cap.d/711.txtadds+1, the line this adds, so the cap is 1476:init's--intosentence names thefamily-landing-in-standardrefusal,make siblingsis described as it stops before cloning, andmembers/has its one-spelling rule (#695) #738 (e62b5e17) cut a line of § Families to fit its own words. validate.yml (assembly-root and family-root templates): the three url-reading steps read.gitmodulesby the rules the doctor andsiblings.pyread it by, so a section git ignores, a section with no path, apathorurlbeginning with-, an unmerged file or a file git refuses whole no longer puts a repository in the App token or the lists, and a refused file fails the step with its cause (#786) #790's786.txtthen filed its own line as+1.README_CAP_FORMAT: "one … file per issue or pull request that grows README.md … line 1+N, N the lines it adds".cd. It did not, and the file says so.#785 (lane 3's #792) had not landed when this was written. It landed next, as
5cbec222, and the merge round below composes the two. It writes "Off Windows, a pathshape-doctor.pycannot print as itself is written$'…'" into this paragraph, and under #711 that is true in Git Bash too. If this lands first, #785 lands its sentence as "Off Windows and in Git Bash", which is three README lines (docs-wording.mdsection 4, which also gives the handbook words and the final blob,e0d2b672). Its cap file is then computed on the README it lands on.#808's review, NIT 6 (
e15c237b)#808 (#759) landed first, as
677f9bfa, so NIT 6 of lane 2's review of it (issuecomment-6236457891) falls to this PR.run_childandrun([PYTHON, ...]), with rows forcli.pyandsetup-project.py(#759) #808'shost_callscounts a host quoter handed on as a value (host_values:map(shell_quoted, w),key=shell_quoted, afunctools.partial). But repo_shape.py and the tools that print paste lines: under Git Bash (MSYSTEMset) the host-quoted lines are quoted forsh, not PowerShell, so a pasted NEXT line works in the shell that printed it (#532, option (a)) #711'scalls_to, whichline_buildersandskips_the_questionread, countedhost_call's calls only. On the merge,' '.join(map(shell_quoted, w)) if os.name == 'nt' else ''indef a(w)gaveset(), while the same line writtenshell_quoted(x) for x in wgave{'a'}.calls_toadds the functionshost_valuesnames.host_valuesnames them asscopesdoes (<module>,Class.method, a def for its defaults and decorators).test_a_quoter_handed_on_as_a_value_beside_os_name_skips_the_questionasserts three things: the call form gives{'a'}; the value form gives{'a'}; and a value use told'posix'(map(functools.partial(shell_quoted, platform='posix'), w)) givesset().return calledin place ofreturn called | {function for function, _ in host_values(tree, names)}(tests/test_readme_windows_quoting_sentence.py:554). The new test fails at its second assert (:657,set() == {'a'}): 1 failed, 191 passed over the quoting file and the trio. Restored; the file's sha256 is82804a0abefore and after.Tests
tests/test_git_bash_dialect.py(new) emulates each host on any host: each module under test gets anoswhosenameis the host's (HostOs, through the function's own__globals__), andMSYSTEMis set, empty or unset in the real environment. The realos.nameis never patched, so pathlib never sees it. Each line is read back byshlex.splitorpowershell_wordsand must give back its words. Hosts: PowerShell (nt, noMSYSTEM), Git Bash (MINGW64), MSYS2 (UCRT64),ntwith an emptyMSYSTEM, POSIX, and POSIX withMSYSTEMset.test_the_question_answers_each_host[6 hosts]MSYSTEMis PowerShelltest_setups_copy_of_the_question_gives_the_same_answeros.names x 8 environments, and the two alphabets are the same patternstest_shell_quoted_spells_a_word_for_the_hosts_shell[2 values x 6 hosts]shell_quotedandshell_linewith no platform: the exact spelling per dialect, read back wholetest_an_explicit_platform_keeps_its_meaning_under_git_bash[2]shell_quotedandquote_argwith"nt"/"posix"under Git Bashtest_setup_quotes_by_the_same_rule_on_each_host[6]shell_wordequalsshell_quotedon every value of the old agreement test, on every host;posix=Truestayssh'stest_setups_family_lines_paste_whole_in_the_hosts_shell[6]family_next_command'sswitch -candaddtest_familys_next_steps_paste_whole_in_the_hosts_shell[6]family.py init'sswitch -c,addandcdtest_the_doctors_lines_paste_whole_in_the_hosts_shell[7 lines x 6 hosts]quote_arg,restore_from_head,bump_leg_command(with its lane trailer),submodule_update_next,checkout_notes,copy_command(cpvsCopy-Item),scaffold_command(python setup-project.pyon a Windows machine in either shell)test_the_doctors_validator_line_sets_pythonpath_in_the_hosts_shell[6]$env:for PowerShell,env PYTHONPATH=forshand Git Bashtest_every_host_dialect_quoter_and_caller_asks_the_one_questionos.nameitself, but thoseREADS_OS_NAME_FOR_THE_MACHINEnames (shape-doctor.py'sscaffold_command). On the base it namesshell_quoted,shell_wordand the seven doctor functions abovetest_reading_os_name_beside_a_host_quoter_skips_the_questionos.nameplatform, a quoter body, a method, and the exemption by fileChanged:
tests/test_family_printed_quoting.py'shost_wordsand the apostrophe test read the host through the question (they reados.name).tests/test_setup_project_py.py::test_the_changed_paste_lines_are_posix_quoted_on_windows_toopatchedSHELL_BARE, which is gone; it unsetsMSYSTEMinstead, which withos.namepatched is what makes the host PowerShell's.Mutants, each one line, applied in a scratch clone, run against the two files above (98 tests) and reverted. All 18 are killed:
MSYSTEMscripts/repo_shape.py:3315test_the_question_answers_each_host[git-bash],test_shell_quoted_spells_a_word_for_the_hosts_shell[...-git-bash], every doctor line[...-git-bash]MSYSTEMcounts as Git Bash ("MSYSTEM" not in environ)scripts/repo_shape.py:3315test_the_question_answers_each_host[empty-msystem],test_setups_copy_of_the_question_gives_the_same_answershell_quotedback onos.namescripts/repo_shape.py:3351test_shell_quoted_spells_a_word_for_the_hosts_shell[...-git-bash],test_familys_next_steps_paste_whole_in_the_hosts_shell[git-bash], the guardtest_every_host_dialect_quoter_and_caller_asks_the_one_questionMSYSTEMsetup-project.py:2605test_setups_copy_of_the_question_gives_the_same_answer,test_setups_family_lines_paste_whole_in_the_hosts_shell[git-bash],test_setup_quotes_by_the_same_rule_on_each_host[git-bash]shell_wordback onos.namesetup-project.py:2629test_setups_family_lines_...[git-bash],test_setup_quotes_...[git-bash], the guardshell_worddropsposix=Truesetup-project.py:2629test_setup_quotes_by_the_same_rule_on_each_host[powershell],[empty-msystem]quote_argback onos.nameshape-doctor.py:822test_the_doctors_lines_...[quote_arg-git-bash],[copy_command-git-bash], the guardrestore_from_headback onos.nameshape-doctor.py:1364[restore_from_head-git-bash],[restore_from_head-msys2-ucrt64], the guardvalidator_commandback onos.nameshape-doctor.py:1825test_the_doctors_validator_line_sets_pythonpath_in_the_hosts_shell[git-bash],[msys2-ucrt64], the guardbump_leg_commandback onos.nameshape-doctor.py:2444[bump_leg_command-git-bash],[...-msys2-ucrt64], the guardcheckout_notesback onos.nameshape-doctor.py:5477[checkout_notes-git-bash],[...-msys2-ucrt64], the guardsubmodule_update_nextback onos.nameshape-doctor.py:5599[submodule_update_next-git-bash],[...-msys2-ucrt64], the guardscaffold_command's quoting back onos.nameshape-doctor.py:9108[scaffold_command-git-bash],[...-msys2-ucrt64](the guard namesscaffold_commandas readingos.namefor the machine, so it is the line test that holds this one)copy_command's branch back onos.nameshape-doctor.py:5911[copy_command-git-bash],[...-msys2-ucrt64], the guardscaffold_commandpicks its entry point by the shell (if target == "nt")shape-doctor.py:9109[scaffold_command-git-bash],[...-msys2-ucrt64](Git Bash would be handedsetup.sh)tests/test_readme_windows_quoting_sentence.py:326test_reading_os_name_beside_a_host_quoter_skips_the_questiontests/test_readme_windows_quoting_sentence.py:328scaffold_command) andtest_reading_os_name_...os.nameread<module>tests/test_readme_windows_quoting_sentence.py:312test_reading_os_name_beside_a_host_quoter_skips_the_questionThe mutants ran in a scratch clone holding the change on bf1f099 (c2d27f48), whose +/- lines are byte-identical to 8d074f1's; the line numbers above are 8d074f1's. Every mutant left the other tests of the two files passing. On Linux no test that existed before can see M1-M15: each reads the real
os.name,posix, and none setsMSYSTEMwith Windows emulated.Runs (targeted sets, one pytest process at a time; the gh guard first on PATH,
GH_TOKENunset, an emptyGH_CONFIG_DIR):Nothing was red before the change. "After" ran the working tree whose tree, committed, is
73966df1; "Final" ran8d074f16itself (A and B started atf578cc02, the same tree before a message-only amend). The list is the brief's: the common set, the five named files, and every test file that namesshell_quoted,shell_line,shell_word,quote_arg,MSYSTEM,git_bash_sessionor a function this changes.tests/test_openreposhape_command.py'stest_a_ref_the_doctor_would_check_out_is_validatedclones this checkout's HEAD, and is refused at the ref before any doctor runs.merge-treeof8d074f16with main741679e7(one later commit,tests/conftest.pyand a new test file) is clean.Fix round 1 (
7581ffb9)From rv711's review of
8d074f16: READY, no MUST. This round takes its three SHOULDs and NITs 1-7. One commit on8d074f16; no rebase.SHOULD 1, pinned copies.
tests/test_family_printed_quoting.pystripshost_shell_dialectfrom the older library too (ADDED_BY_711, besideADDED_BY_486).tests/test_pinned_copy_text.py's skew test now reads every pinned script that imports fromrepo_shape. That addssiblings.py, bothbootstrap.pyfiles andvalidate-manifest.pytovalidate-pins.py,validate-repository-naming.pyandvalidate-family.py. It refuseshost_shell_dialectas it refusesshown_path.validate-manifest.pyis a fourth addition beyond the three named, because it too is pinned and importsrepo_shape.SHOULD 2, the guard.
tests/test_readme_windows_quoting_sentence.py'sline_buildersfinds, per file, every function that takes aplatformand hands it to a host-dialect quoter or to another such function. In the doctor that is the nine:restore_from_head,validator_command,bump_leg_command,checkout_notes,kept_branch_line,submodule_update_next,lane_trailer_command,copy_commandandscaffold_command.skips_the_questioncounts calls to them as it counts the four quoters, and names a quoter's body byClass.method.copy_command(s, d, os.name)added toshape-doctor.pyas a new row is now flagged.copy_commandis defined, is not, because builders are found per file. The test asserts both.sys.platformbranch;from os import name; a module constant built fromos.name; a builder imported from another file; and a branch onos.namein a function that calls no quoter and no builder.SHOULD 3 and NIT 7, docs.
docs-wording.mdis recomputed at README, AGENTS.md, docs/cli.md and the handbook:pipx install openreposhapeis the second way in and the first on native Windows, git-filter-repo is installed in install.sh's order and never with a barepip install, and a first draft of the v1.1.0 release notes (#563) #630's headb2a62ec7:45a3f886→4c523ffe(it wasb4fd89e5beforecdwas named);711.txt+1.It adds
init'scdline tofamily.py's list, and the handbook says cmd.exe is handed PowerShell's quoting. A section on shape-doctor.py: every non-ASCII character in a path it prints becomes?, so a NEXT or keep line naming such a path (a membercafé, a holder underProjets/Équipe) cannot be pasted as printed #785 gives the words for both orders. Whichever lands second rewords shape-doctor.py: every non-ASCII character in a path it prints becomes?, so a NEXT or keep line naming such a path (a membercafé, a holder underProjets/Équipe) cannot be pasted as printed #785's "Off Windows" to "Off Windows and in Git Bash", not "Outside PowerShell", because cmd.exe is handed PowerShell's quoting. Both orders end at README 1479 and blobe0d2b672.NIT 1.
tests/test_git_bash_dialect.py'scheckout_notesrow passes a kept repository, sokept_branch_line'sgit --git-dir=… branch NAME <commit>is read back per host.LANE_NAME_REatscripts/shape_materialize.py:2256refuses one, andlane_trailerreturnsNoneforDana's lane. So every real trailer reads'Lane: …'alike in both dialects, and the asked-forLANES_LANEwith an apostrophe would print no trailer at all.test_the_lane_trailer_is_spelled_by_the_lines_own_quoterhands the trailer's quoterLane: Dana's lanethrough a stand-inlane_trailer_argument. Thebump_leg_commandrow keeps the reallane 2.NITs 2-5, comments only.
setup-project.py'sSHELL_BARE_NTcomment namestests/test_git_bash_dialect.py.tests/test_shape_doctor.py:9585-9588saysquote_arg(value, None)askshost_shell_dialect.tests/test_git_bash_dialect.pysays no CI job runs these lines under a real Git Bash:tests-git-bashruns only the launcher and installer suites.scripts/repo_shape.py's module docstring no longer sayssiblings.pyusesshell_quoted.Mutants of this round, each one line, applied to the worktree by a copy of rv711's
mutate.py(P1, P2, D5, D6 are rv711's), run and reverted with a sha256 check. All 7 are killed:siblings.pyimportshost_shell_dialecttemplates/family-root/scripts/siblings.py:188test_siblings_runs_beside_a_repo_shape_without_the_quoter,test_no_pinned_script_imports_what_an_older_library_lacks[siblings]validate-pins.pyimports ittemplates/assembly-root/scripts/validate-pins.py:81test_no_pinned_script_imports_what_an_older_library_lacks[validate-pins]kept_branch_line(entry, "nt")shape-doctor.py:5483test_the_doctors_lines_paste_whole_in_the_hosts_shell[checkout_notes-git-bash],[-msys2-ucrt64],[-posix],[-posix-with-msystem]lane_trailer_command("nt")shape-doctor.py:2449test_the_lane_trailer_is_spelled_by_the_lines_own_quoter[git-bash],[msys2-ucrt64],[posix],[posix-with-msystem]tests/test_readme_windows_quoting_sentence.py:360test_os_name_handed_to_a_line_builder_skips_the_questionplatformto another builder is not onetests/test_readme_windows_quoting_sentence.py:346test_os_name_handed_to_a_line_builder_skips_the_questiontests/test_readme_windows_quoting_sentence.py:362test_reading_os_name_beside_a_host_quoter_skips_the_questionRuns of this round, on
7581ffb9: set A (the 18 files of the final A above) 995 passed (300.73s), 984 before plus 11 new: the trailer test's 6 hosts, the builder test, and the skew test's 4 new scripts. Before committing, the changed tests alone: 113 passed.tests/test_shape_doctor.py,setup-project.pyandscripts/repo_shape.pychanged only in comments and a docstring, so B and C were not re-run; the three parse.Merge round after #792 (#785) (
4ed7d2e0)Lane 3's #792 (#785) landed as
5cbec222. It writes into the README paragraph, the handbook panel and thequote_argthis PR changes. Maind7befb91holds it, with #826, #781, #833, #851, #756 and #825, and is merged as4ed7d2e0("Merge origin/main (d7befb9) into fix/711-git-bash-dialect after #792 (#785) landed"). Three files conflicted, and each hunk is composed with no change to either side's behaviour:shape-doctor.pyrepo_shapeimporthost_shell_dialectPOWERSHELL_SINGLE_QUOTESshape-doctor.pyquote_argwindowsfromhost_shell_dialect();textstill the base'sascii_text(str(value))text = str(value),windowsfromos.name, and a value that is not ASCII goes toquoted_beyond_asciitextand its branch, with #711'swindowsREADME.mddocs-wording.mdsection 4)docs/handbook.htmlpanel-windowsMSYSTEMlimitdocs/handbook.html§ Starting a project in a new organisation4c523ffe3d4a01ffe0d2b672, the merged section's blob (docs-wording.mdcomputed the same)The "cmd.exe" wording comes from the review of
5035128e. "And cmd.exe is handed PowerShell's" read as a cmd.exe started inside Git Bash too, and that one inheritsMSYSTEMand is answeredsh's. README names no cmd.exe, so it does not change there.SENTENCEmatches once in README's section and once in the panel, andhandbook_marker_problemsfinds nothing. The markers are at :475, :615, :1095, :1132, :1195, :1451 and :1750.quote_arg, composed. A probe called the realhost_shell_dialectwith each host'sos.nameand environment, then the mergedquote_arg. The report'sencodingwas ASCII, where the path cannot be printed as itself, or UTF-8, where it can:host_shell_dialect()C:\Users\Zoë\work, ASCII reportC:\work\Atlasnt, noMSYSTEM)nt('C:\Users\Zo' + [char]0x00EB + '\work')'C:\Users\Zoë\work'C:\work\Atlasnt, noMSYSTEM)nt('C:\Users\Zo' + [char]0x00EB + '\work')'C:\Users\Zoë\work'C:\work\Atlasnt,MSYSTEM=MINGW64)posix$'C:\134Users\134Zo\303\253\134work''C:\Users\Zoë\work''C:\work\Atlas'posix$'C:\134Users\134Zo\303\253\134work''C:\Users\Zoë\work''C:\work\Atlas'That is what README now says:
$'…'off Windows and in Git Bash, and PowerShell's quoting in PowerShell and cmd.exe. A path the report can show is quoted as itself in every dialect, and an ASCII value is quoted exactly as before.os.nameread inshape-doctor.pyis shape-doctor.py: a.gitmodulessection name keeps git's own bytes and is handed back to git as them, soconfigured_urlandkept_gitdirfind a member namedcaf\xe9, its detached commit gets its keep line under any locale, and a non-ASCII name no longer ends--rootin a UnicodeEncodeError traceback underLC_ALL=C PYTHONUTF8=0(#732) #774'sgit_bytes. It picks the type of git's argv (text on Windows, bytes elsewhere) and calls no quoter, so the one-question guard rightly leaves it alone.GIT_CONFIG_COUNTentries the environment holds, so a pasted line keeps the session's maintenance settings and the Windows job's two (#758) #818'sPASTE_ENVandALLOW_FILE_PROTOCOLare in none of this PR's files.The cap.
d7befb91, README is 1476 lines and the cap is 1422 + 2 (460.txt) + 5 (550.txt) + 12 (563.txt) + 22 (564.txt) + 10 (606.txt) + 1 (683.txt) + 2 (785.txt) + 1 (786.txt) = 1477.?, so a NEXT or keep line naming such a path (a membercafé, a holder underProjets/Équipe) cannot be pasted as printed #785's reworded sentence. Sotests/readme-cap.d/711.txtis+2; README is 1478 and the cap 1479.README.md is 1478 lines; the cap is 1422 + 2 (460.txt) + 5 (550.txt) + 12 (563.txt) + 22 (564.txt) + 10 (606.txt) + 1 (683.txt) + 2 (711.txt) + 2 (785.txt) + 1 (786.txt) = 1479Runs of the merge round (the same settings as above). The set is #711's own tests, #792's three (
test_doctor_non_ascii_paths.py,test_shape_doctor.py,test_stdout_encoding.py), the 21 README and handbook readers, the quoting test, the trio, and every test file that namesquote_argorhost_shell_dialect: 29 files.ed052f2atest_git_bash_dialect.pyon main)4ed7d2e0's treetest_git_bash_dialect.py,test_doctor_non_ascii_paths.py4ed7d2e0test_git_bash_dialect.py, and 8 that #711 adds to the quoting test (4) and to the pinned-copy skew test (4)Main moved from
ed052f2atod7befb91before the merge. Its four new commits (#833, #851, #756, #825) touch none of the three conflicted files;setup-project.py, which #851 and #756 change, merged clean.Main moved again while the "after" pass ran, to
d6475f96(#7880905eef5, which changesshape-doctor.pyandsiblings.py; #8226fbf3301; #844d6475f96). None of the three touches README, the handbook,tests/readme-cap.d/or the quoting test, andgit merge-treeof4ed7d2e0with it is clean. It is not merged here.Runs of the README turn
Targeted sets, one pytest process at a time, with
GH_TOKENunset, an emptyGH_CONFIG_DIRandS/frbinfirst on PATH.tests/test_readme_*.py, every test filegit grep -l -e 'README.md' -e 'handbook.html' -- tests/lists (91 on0357e7f4, 94 on7564e916), and the trio.test_cli_reference.py, and repo_shape.py and the tools that print paste lines: under Git Bash (MSYSTEMset) the host-quoted lines are quoted forsh, not PowerShell, so a pasted NEXT line works in the shell that printed it (#532, option (a)) #711'stest_git_bash_dialect.py,test_family_printed_quoting.py,test_setup_project_py.pyandtest_pinned_copy_text.py.0357e7f4(main677f9bfa)1a285512(main5fc049b7)7564e916(mainb9b70692)5035128e's treetest_readme_sections.py,test_readme_windows_quoting_sentence.pyand the trioe15c237b's treetest_readme_windows_quoting_sentence.pyand the trioreturn calledat:554e15c237bNothing was red before. The cap test on
5035128eprints:README.md is 1475 lines; the cap is 1422 + 2 (460.txt) + 5 (550.txt) + 12 (563.txt) + 22 (564.txt) + 10 (606.txt) + 1 (683.txt) + 1 (711.txt) + 1 (786.txt) = 1476.#818 removed
PASTE_ENVandALLOW_FILE_PROTOCOL. Neither name is in any of #821's nine files after the merges; the one mention left intests/is a docstring in main'stests/test_no_auto_maintenance.py:533.Main moved to
ca47b71dwhile the "after" pass ran (#77452ff5962, #83051414d2e, #841ca47b71d). None of the three touches README, the handbook,tests/readme-cap.d/or the quoting test, andgit merge-treeofe15c237bwith it is clean. It is not merged here.Not changed
os.namesites that are not the shell, left as they are (line numbers at8d074f16):scripts/repo_shape.py:135andsetup-project.py:126(PYTHON,PYTHON_CMD: Git Bash runs the Windows Python, whose command ispython); the doctor'spython_command,offline(shape-doctor.py:1405, a Windows environment variable is one name whatever its case) andscaffold_command's entry point;setup-project.py:841(the install offer's argv,cmd /corbash -c),:1098(thecore.autocrlfpreflight),:1372(the controlling terminal),:2537and:2558(the clone'score.autocrlf);adopt-project.py:1042,:1060,:1094,:1128(path and argv encoding),:5899,:7693(drive-letter paths);scripts/shape_materialize.py:1994(a commit message's argv encoding);src/openreposhape/standard.py:275(the cache directory),:310(PATHEXT),:446(rename retries);scripts/render-cli-reference.py:750(whether there is a bash to render with).update-shape.py,adopt-project.py,scripts/bump-leg.py,templates/family-root/scripts/validate-family.py,templates/assembly-root/scripts/validate-pins.pyandbootstrap.pyquote withshlex;scaffold-project.pypasses"posix";scripts/family.py'srm -rf,git mv --sparse,mvandshlexlines aresh's on every host. None calls a host-dialect quoter with no platform.templates/family-root/scripts/siblings.pyimports the holder's ownrepo_shape.pyand quotes itsmvlines withshlex.join, POSIX on every host. It must not importhost_shell_dialect, because a holder may keep an olderrepo_shape.py. Until fix round 1 nothing held that (rv711's SHOULD 1):test_siblings_runs_beside_a_repo_shape_without_the_quoterran it beside a copy that still had the new name. It now stripshost_shell_dialecttoo (ADDED_BY_711), andtests/test_pinned_copy_text.py's skew test refuses the name in every pinned script that imports fromrepo_shape.src/openreposhape/has no quoter;cli.pyandstandard.pyprint no host-quoted line.scripts/repo_shape.pyorsiblings.py: a project'scontracts/shape-pin.yamldigests are computed from the live file at scaffold time (scripts/shape_materialize.py'sCOPIED_FROM_SHAPE), andtests/test_pinned_copy_text.py's skew rule (no pinned script imports what an older library lacks) holds, because no pinned script changes; since fix round 1 it reads all seven pinned scripts that import fromrepo_shapeand refuseshost_shell_dialectas it refusesshown_path. The copiedscripts/repo_shape.pychanges, so it rides in v1.1.1's shape bump; no digest is edited.docs/cli.mdsay nothing about which shell a printed line is quoted for, and do not change.os.name, left alone:tests/test_shape_doctor.py:3638,:9285,:9436,:9590(its comment at :9585-9588 now saysquote_arg(value, None)askshost_shell_dialect),:9613-9622,:11037,:11125(line numbers at7581ffb9), andtests/test_doctor_unfinished_merge.py:54. They hold whereverMSYSTEMis unset, which is every CI job that runs them (Linux), and pass here. A whole-suite run from inside Git Bash would fail them, and the validator and scaffold ones cannot be fixed by swapping the question in, because Git Bash is a third pair (Windows machine,shshell) that no explicitplatformnames. A checklist candidate, not this change:tests/test_shape_doctor.pyis a hot file.Closes #711
🤖 Generated with Claude Code
Summary by Sourcery
Make generated Windows commands use the quoting rules of the shell that prints them, including POSIX quoting under Git Bash and MSYS2.
New Features:
Bug Fixes:
Enhancements:
Documentation:
Tests: