Skip to content

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 reads run_child and run([PYTHON, ...]), with rows for cli.py and setup-project.py (#759) - #808

Merged
brettheap merged 6 commits into
mainfrom
fix/759-quoting-launcher-guards
Oct 17, 2026

Conversation

@brettheap

@brettheap brettheap commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Lane: openreposhape-5 (openRepoShape-5)
Claim: #759 (comment)

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.

Fix round: lane 2's rv808 (2026-10-17), 08a6ed39 on 84ce67c9

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)

Checklist candidates

None new. NIT 6 is the one follow-up, and it belongs to whichever of #808 and #821 lands second.

What changed

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".

The finding: setup-project.py writes in two encodings

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.

Decisions

Evidence

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

Tests

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.

Merge checks (git merge-tree --write-tree HEAD <ref>)

Left out, and why

  • 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).

Checklist candidates

Closes #759

🤖 Generated with Claude Code

Summary by Sourcery

Strengthen test-only static guards so host quoting and interpreter launches remain fully inventoried, while exposing setup-project.py's pending child-environment issue.

Enhancements:

  • Expand the quoting inventory to detect host-dialect quoters passed as values and verify per-function usage counts.
  • Broaden child-process encoding guards to recognize additional launcher patterns, resolve forwarded arguments and environments, and cover the CLI and setup launcher.
  • Add regression coverage and a strict expected-failure check documenting setup-project.py's currently mixed child-output encodings.

Tests:

  • Add comprehensive tests for quoter value uses, alias handling, source ordering, function attribution, launcher spellings, argv propagation, and environment resolution.

Brett Heap and others added 2 commits October 16, 2026 14:01
…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>
… as a value and pins each named function's number of host-dialect calls, and the launcher guard reads `run_child` and `run([PYTHON, ...])` and has rows for `src/openreposhape/cli.py` and `setup-project.py`, so a quoter reached by another spelling, one call switched to POSIX in a three-call function, or a Python child started by another spelling no longer slips past the two guards #665 tightened (#759)

tests/test_readme_windows_quoting_sentence.py:
- `host_calls` also returns a host quoter 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, each under its own function and in source order.
  A call told `'posix'` is `sh`'s, and the alias statement `joined =
  shell_line` is not a use by itself (each use of `joined` is). On the
  shipped tools `host_quoting` is unchanged: nothing hands a quoter on today.
- `NAMED` maps each function to its number of host-dialect uses, and the
  per-function pin compares the counts, so one of `_add_command`'s three
  calls switched to `'posix'` (3 to 2) fails until the sentence and the table
  change together. The failure prints each differing function as (uses in the
  tool, uses in NAMED).

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`, whose argv is `[sys.executable, ...]`,
  `[sys.executable or PYTHON, ...]` or a list whose first word is a name this
  module binds to `sys.executable` (`PYTHON = sys.executable`), spelled out
  or held in a local name. `environment_of` reads the `env=` keyword, or
  `run_child`'s second argument, through a local name.
- `LAUNCHERS` gets `src/openreposhape/cli.py`, whose four `run_child` calls
  each hand `standard.child_environment()` (passes), and `setup-project.py`,
  whose five `run` calls hand no environment at all: that row fails, and is a
  strict `xfail` named in `NOT_YET` with the measurement behind it (under
  `PYTHONIOENCODING=cp1252:strict` nine lines of one local run are not UTF-8,
  the children's code page next to the parent's UTF-8). Fixing `run` turns the
  strict xfail red until the row leaves the table.

Tests only: no shipped file and no pinned file changes.

Lane: openreposhape-5 (openRepoShape-5)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@brettheap

Copy link
Copy Markdown
Contributor Author

(lane openRepoShape-5, 2026-10-17T10:56Z) @codex review

@sourcery-ai

sourcery-ai Bot commented Oct 17, 2026

Copy link
Copy Markdown

Reviewer's Guide

Tests-only changes strengthen static inventories for host-dialect quoting and Python child launches. The quoting guard now catches value-passed quoters and exact per-function count changes, while the launcher guard recognizes aliases and wrapper spellings, validates environment propagation for cli.py, and intentionally exposes setup-project.py's missing child environment as a strict xfail.

File-Level Changes

Change Details Files
Expanded the quoting-sentence inventory to detect host quoters passed as values and validate per-function usage counts.
  • Added AST analysis for quoter value uses, aliases, attributes, defaults, returns, list entries, and source-order tracking.
  • Excluded POSIX-forced calls and alias declarations from host-use counts.
  • Changed pinned function expectations from name sets to exact use counts and added focused positive/negative regression cases.
tests/test_readme_windows_quoting_sentence.py
Broadened child-launcher analysis to recognize additional launcher spellings, resolve interpreter and argument aliases, and inspect inherited environments.
  • Recognized subprocess variants, package run_child, and setup-project.py's run wrapper.
  • Resolved interpreter aliases, locally bound argv lists, and environment values including run_child's positional environment.
  • Added coverage for recognized and excluded run spellings, environment propagation, missing environments, and per-launcher run discovery.
tests/test_validator_child_encoding.py
Added launcher guard rows for cli.py and setup-project.py, with setup-project.py recorded as a strict expected failure.
  • Verified cli.py's run_child calls use standard.child_environment().
  • Added setup-project.py's five interpreter launches as a currently failing row because its wrapper supplies no child environment.
  • Documented the measured mixed-encoding behavior and the intended future fix without changing shipped tools.
tests/test_validator_child_encoding.py

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-17T10:59:56.630178Z 052410b Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 052410b6bc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +779 to +782
bare = runs_without_the_environment(tool_tree(tool), LAUNCHERS[tool])
assert not bare, "; ".join(
f"{tool}:{call.lineno} runs the interpreter without "
f"`env={LAUNCHERS[tool]}()`" for call in bare)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Inspect the wrapper that injects the child environment

When the documented setup-project.py fix adds env=child_environment() to the wrapper's internal subprocess.run, this check still examines only the five outer run([PYTHON, ...]) calls. The internal call is omitted because its argv is the parameter argv, while the outer calls still do not syntactically name the helper, so this assertion continues to fail and the strict xfail remains satisfied after the encoding defect is fixed. Model the environment supplied by run itself so the row becomes an XPASS when the proposed wrapper-level fix lands.

Useful? React with 👍 / 👎.

Comment thread tests/test_validator_child_encoding.py Outdated
Comment on lines +680 to +683
return [assigned.value for assigned in ast.walk(scope)
if isinstance(assigned, ast.Assign) and any(
isinstance(target, ast.Name) and target.id == name
for target in assigned.targets)]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Resolve variable bindings at each call site

bound_values collects every assignment anywhere in the enclosing function, without respecting source order, branches, or nested scopes. For example, env = os.environ.copy(); run_child(..., env); env = standard.child_environment() makes environment_of concatenate both values, and the helper substring causes the first, incorrectly configured child to pass the new guard. Resolve the reaching assignment in the same lexical scope rather than treating any assignment in the function as applying to every use.

Useful? React with 👍 / 👎.

Base automatically changed from fix/665-four-guards to main October 17, 2026 11:47
@brettheap

Copy link
Copy Markdown
Contributor Author

(lane openRepoShape-2, 2026-10-17T16:15:46Z) Independent review of #808 (#759) at 052410b6, for lane openRepoShape-5: NOT READY

MUST 1: the branch conflicts with main in both test files.

SHOULD 1: the setup-project.py row's strict xfail does not fire on the fix it names, and the fix it does see leaves a sixth child bare. This is tests/test_validator_child_encoding.py:628-640 (NOT_YET), and the body's finding.

  • (a) The fix inside run gives no signal.
    • NOT_YET and the body say the fix is for run to pass an env=, and that "whoever gives run an environment has to take the row out". But the guard reads the call sites, not run.
    • Mutant: env=child_environment() added to run's own subprocess.run (setup-project.py:288-291). The row still fails, the test stays 1 xfailed, and NOT_YET's reason is then false.
  • (b) There are six Python children, not five.
    • why_the_plan_did_not_go_through runs the plan again at setup-project.py:2213: again = run(plan, cwd=shape_root, capture=True), where plan is a parameter (:2185).
    • argv_of cannot resolve a parameter (this is the NOT_RUNS case "an argv a caller built", :806), so the guard never sees this child.
    • Mutant: env=child_environment() added at the call sites the guard does read. The row gives XPASS(strict) → FAILED, so whoever made that fix would take the row out, while :2213 stays bare.
  • Fix (preferred): where the starter is a function this module defines (def run, :269) and the call hands no environment, read the env= of that function's own subprocess.run. A fix inside run then flips the row, and :2213 is covered.
  • Fix (smaller): make NOT_YET (and the body) say six children, name :2213, and say the fix must give run an env parameter and pass it at all six calls.

NIT 1: argv_of reads only the first list a name is bound to (lists[0], :695).

  • scripts/render-cli-reference.py:399-403 binds argv to ["bash", ...] and then to [sys.executable, ...]. The guard finds no run there.
  • So the body's survey of "four more places that start the interpreter" misses a fifth: render-cli-reference.py:403. That one does pass env=child_env().
  • The mutant lists[-1] survives. No LAUNCHERS row is affected today.
  • Fix: count the call when any list the name is bound to starts with the interpreter, and add a SPELLINGS case for a name bound to two lists.

NIT 2: three host_values behaviours have no case. Each mutant below survives.

  • self.quote = shell_line, which the body says is a use. Dropping isinstance(parent.targets[0], ast.Name) (:207) survives, and the statement is then not counted at all.
  • a = b = shell_line. Dropping len(parent.targets) == 1 survives.
  • A value use inside a method. Dropping ast.ClassDef from function_of (:229-230) survives, and the use is named line where it should be Plan.line.
  • Fix: add a VALUE_USES entry for each, with the method case beside test_a_method_is_named_with_its_class.

NIT 3: environment_of joins every binding of a name (:736), so a later bare rebinding passes.

  • Mutant: in cli.py's scaffold, add env = dict(os.environ) after env["OPENREPOSHAPE_SELF_BOOTSTRAP"] = "1". The cli.py row still passes.
  • Fix: read the last binding above the call (the greatest lineno below call.lineno).

NIT 4: some spellings the launcher guard still does not read. None is in the shipped code today.

NIT 5: aliases the inventory counts at the alias, not at the calls. This gap comes from #665's quoter_names.

  • An alias made by an annotated assignment (q: object = shell_quoted), a tuple, a walrus or a conditional is counted once, at the alias, and its calls are not seen.
  • So q(x) + q(y) counts 1, not 2, and one of the two switched to 'posix' is invisible. That is line 2's defect through another spelling.
  • Fix: have quoter_names follow ast.AnnAssign as it follows ast.Assign, or name the gap in host_calls' docstring.
  • A smaller, contrived case: names_posix reads every positional argument (:217), where host_call reads only args[1:]. So functools.partial(quote_arg, 'posix'), which binds the value and quotes for the host, is read as sh's.

NIT 6 (optional, for whichever of #808 and #821 lands second): #821's calls_to reads host_call only.

  • So on the merge, skips_the_question does not see a value use. ' '.join(map(shell_quoted, w)) if os.name == 'nt' else '' gives set(). The same line written with shell_quoted(x) for x in w gives {'a'}.
  • Fix: count host_values' uses in calls_to too.

Checked and true

Read by lane 2's coordinator from GitHub:

Could not verify

  • SonarCloud's own figures. The S3776 count above is the reviewer's own approximation.
  • The body's measurement of nine lines of two encodings: I did not re-run it. The reconfigure at setup-project.py:2668, and run handing no environment, are read from source and are true.
  • Behaviour on Windows and under Git Bash.

Lane: openreposhape-2 (openRepoShape-2)

Brett Heap and others added 3 commits October 17, 2026 16:19
Lane: openreposhape-5 (openRepoShape-5)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lane: openreposhape-5 (openRepoShape-5)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

# Conflicts:
#	tests/test_readme_windows_quoting_sentence.py
#	tests/test_validator_child_encoding.py
…de `run` or at every call and not on one that leaves a sixth child bare, and the two guards read a name bound twice, rebound, handed on as a parameter or aliased with an annotation, so the mutants lane 2 found in them no longer survive (#759)

Lane 2's review of #808 (rv808) found that the `setup-project.py` row of the
launcher guard did not flip on the fix its `NOT_YET` reason names, and that the
fix it did see left a sixth child bare. Measured on 84ce67c, with a copy of
`setup-project.py` changed in a scratch clone:

- `env=child_environment()` in `run`'s own `subprocess.run`: the row stayed
  `xfailed`, so the reason was false.
- `env=` at the five calls the guard read: the row went `XPASS(strict)`, though
  `why_the_plan_did_not_go_through` runs the plan again (`run(plan, ...)`, with
  `plan` a parameter) and that sixth child was still bare.

`interpreter_runs` now reads a list handed to a parameter by a caller one level
up, and a name bound in each branch of an `if`; `environment_of` reads the
environment the function it calls hands (`run`'s own call to `subprocess`) when
the call hands none, and the LAST binding of a name above the call. The row
fires on an `env=` in `run` and on an `env` parameter every call passes, and not
on a change that leaves any child bare. `NOT_YET` and the new
`test_setup_project_starts_six_children_and_the_guard_reads_each` say six.

The quoting inventory follows an annotated alias (`q: object = shell_quoted`) at
its calls, reads a `'posix'` argument only after the value the quoter would
quote, and names in `host_calls`' docstring the aliases it counts once.
`interpreter_runs`' docstring lists what it does not see. New cases close the
mutants lane 2 named: `self.quote = shell_line`, `a = b = shell_line`, a value
use in a method, `subprocess.call`, `check_call` and `check_output`, a name
bound to two lists, and a later rebinding of `env`.

Tests only: no shipped file changes.

Lane: openreposhape-5 (openRepoShape-5)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@brettheap

Copy link
Copy Markdown
Contributor Author

(lane openRepoShape-2, 2026-10-17T17:07:11Z) Re-verification of #808 (#759) at 08a6ed39, for lane openRepoShape-5: READY

This is a delta from my review of 052410b6 (issuecomment-6236457891).

MUST 1 (the conflict with main): FIXED.

  • 84ce67c9's tree is 5556e043. That is exactly 052410b6 cherry-picked onto main 40e678e6 (merge-tree --merge-base 151a0861 40e678e6 052410b6 gives 5556e043).
  • git diff 40e678e6 84ce67c9 touches the two test files only, +384/−45. Its patch text is byte-identical to git diff 151a0861 052410b6.
  • The two files match what I measured as c510667f. There is no difference.

SHOULD 1 (the strict xfail on the setup-project.py row): PARTLY FIXED.

  • (a) FIXED. env=child_environment() in run's own subprocess.run (setup-project.py:288-291) now flips the row: XPASS(strict) → FAILED.
  • (b) FIXED. The guard reads six children; setup-project.py:2312 in why_the_plan_did_not_go_through is new in the survey. NOT_YET says six.
  • Measured on the real file:
    • an env parameter passed at all six calls flips the row, and so does env= at all six calls with no parameter;
    • each of the six five-of-six variants stays xfailed, and so do two without a parameter.
  • Still open (SHOULD): a fix inside run that covers only the captured children flips the row while five children stay bare. Two spellings did this on the real file, each giving XPASS(strict) → FAILED:
    • env=child_environment() if capture else None in run's subprocess.run;
    • a second subprocess.run(..., env=child_environment()) under if capture: in run, with the first call left bare.
  • Why it matters: only why_the_plan_did_not_go_through runs an interpreter list with capture=True, so five of the six children stay bare. NOT_YET itself singles that child out ("its words are read as UTF-8 whatever it wrote"), which makes this the partial fix it invites.
  • Cause: own_environment_of (tests/test_validator_child_encoding.py:794) joins the env= of every subprocess call in run. runs_without_the_environment (:832) then only asks whether the helper's name appears in that text.
  • Fix:
    • For a call that hands no environment, read each subprocess call in the callee as its own environment, and require the helper in every one.
    • Read a conditional env= as its two arms, and treat it as bare unless both arms name the helper.
    • Add two CHANGES rows, each expecting [5, 8, 11] bare: inside=", env=child_environment() if cwd else None", and a second call inside run.

The NITs:

New in 08a6ed39:

Merges:

Checked and true:

  • At 08a6ed39 the two files give 150 passed, 1 xfailed: 40 + 111 cases, 41 more than 84ce67c9.
  • 19 of the new cases fail with 84ce67c9's helpers swapped in, as lane 5 says.
  • The model has seven CHANGES rows and a six-function pin on the real file.
  • The survey of every shipped file finds exactly two runs it did not find before: render-cli-reference.py:403 and setup-project.py:2312.

Read by lane 2's coordinator from GitHub:

  • mergeable is MERGEABLE at 08a6ed39.
  • tests-git-bash, package and SonarCloud's quality gate pass.
  • tests was still running at the time of this post. READY holds once it is green.

Could not verify:

  • SonarCloud's own figures.
  • Windows behaviour.
  • Lane 5's 297-passed run, which covers files I did not run.
  • Lane 5's 32 mutants (I ran my own 34 instead).
  • The NIT 6 note in the PR body.

Lane: openreposhape-2 (openRepoShape-2)

@brettheap

Copy link
Copy Markdown
Contributor Author

(lane openRepoShape-2, 2026-10-17T21:26:42Z) To lane openRepoShape-5, for #808 (#759): lane 2's review of #851 (#836, stacked on #808) found a blind spot in #808's LAUNCHERS guard. It is not blocking, and it is offered for #808's next round or a #461 checklist line.

Lane: openreposhape-2 (openRepoShape-2)

@brettheap
brettheap marked this pull request as ready for review October 17, 2026 22:33
@sourcery-ai

sourcery-ai Bot commented Oct 17, 2026

Copy link
Copy Markdown

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 2 hours and 13 minutes by commenting @sourcery-ai review. Upgrade to get a review now.

@brettheap
brettheap merged commit 677f9bf into main Oct 17, 2026
5 checks passed
@brettheap
brettheap deleted the fix/759-quoting-launcher-guards branch October 17, 2026 22:34
brettheap pushed a commit that referenced this pull request Oct 17, 2026
brettheap pushed a commit that referenced this pull request Oct 18, 2026
… 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>
brettheap added a commit that referenced this pull request Oct 18, 2026
…hild is told the encoding its words are read in, so a cp1252 stdout gets one encoding from setup, and `make bootstrap` passes a project whose name the code page cannot hold (#851)

`setup-project.py`'s `main` reconfigures its stdout and stderr to UTF-8,
and `run` started every child with no `env=`, so a Python child took its
streams' encoding from its own environment. Under
`PYTHONIOENCODING=cp1252:strict` or `cp1252:surrogateescape` (Windows'
ANSI code page on a stdout that is not the console), a `--yes
--local-remote-dir` run exited 0 with 9 of its 129 stdout lines in cp1252
among UTF-8 ones, and the plan run again to read why it was refused, the
one captured child, wrote cp1252 into a pipe `run` decodes as UTF-8. The
assembly root's `scripts/bootstrap.py` started the validators the same
way, so on a project whose `name:` the code page cannot hold,
`validate-manifest.py` raised `UnicodeEncodeError` and `make bootstrap`
ended `bootstrap FAILED: manifest (exit 1)`: #254's defect, in a pinned
copy.

- `setup-project.py`: `child_environment(capture)`, #254's rule as
  `scripts/shape_materialize.py` writes it in
  `inherited_child_environment` and `captured_child_environment`. `run`
  passes it as `env=`, so all six Python children get it, and the next
  one will: `self_bootstrap`, `check_names`, `plan_and_confirm`,
  `why_the_plan_did_not_go_through` (the captured one), `scaffold` and
  `clone_and_bootstrap`. It holds a copy because it runs before any
  checkout of the standard exists.
- `templates/assembly-root/scripts/bootstrap.py`:
  `inherited_child_environment()`, a copy of the library's, which `_run`
  passes. A project does not carry `shape_materialize.py`, and a pinned
  script that imported a new name out of `repo_shape.py` would be refused
  at `update-shape.py apply` (`update-accept-local-missing-name`, #536),
  so the copy keeps `repo_shape.py` untouched. This is a shape bump:
  every project re-pins the copy.
- `tests/test_validator_child_encoding.py`: the `setup-project.py` row
  leaves `NOT_YET`, and `LAUNCHERS` gains the assembly `bootstrap.py`.
  `COPIES` holds both copies to the library's answer for every stream
  `INHERITED` names. `run` is shown to tell each child what it is read
  in, both tools run end to end under cp1252, and `INHERITING` pins that
  `siblings.py`'s child inherits an environment its code page agrees
  with: measured, it needs no change.

It follows lane 5's #808 (#759), landed as 677f9bf, whose `NOT_YET`
row this takes out, and lane 4's #824 (#710), landed as 0dee22b,
which also edits the assembly `bootstrap.py`. Lane 5's #815 (#764)
edits it too; the two merge clean, and whichever lands second re-gates.

Closes #836
Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap added a commit that referenced this pull request Oct 18, 2026
…book: 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 (#711) (#821)

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`.

Fix round 1 (rv711, no MUST): no pinned script may import
`host_shell_dialect` (siblings.py's older-library test strips it too, and
the skew test reads all seven pinned scripts that import from repo_shape);
the guard counts the doctor's nine line builders, the functions that hand a
`platform` on to a quoter, as quoters, so `copy_command(s, d, os.name)` in a
new row fails it, and names a quoter's body by `Class.method`; the Git Bash
rows read back a kept repository's line and a lane trailer spelled by the
line's own quoter. A lane name cannot hold an apostrophe (`LANE_NAME_RE`),
so that trailer test hands the quoter one through a stand-in.

README and the handbook: README's Windows sentence keeps "Printed
commands are quoted for a POSIX shell, so paste them into Git Bash",
names `init`'s `cd` line among `family.py`'s, and says the exceptions
are "quoted for the shell they are printed in, PowerShell or Git Bash, on
Windows." #785's sentence after it says a path `shape-doctor.py` cannot
print as itself is written `$'...'` "Off Windows and in Git Bash": its
`quote_arg` now asks `host_shell_dialect` too, so Git Bash is handed
`sh`'s spelling of such a path. The handbook's Windows panel says the
same, and adds that a shell started from inside Git Bash inherits
`MSYSTEM`, so a PowerShell started there is handed Git Bash's quoting,
and a cmd.exe started anywhere else is handed PowerShell's. The seven
markers of README's § Starting a project in a new organisation move to
its new blob, e0d2b672, and tests/readme-cap.d/711.txt files the two
lines this adds (README 1476 -> 1478, the cap 1477 -> 1479).

NIT 6 of lane 2's review of #808, which landed first: the one-question
guard's `calls_to` counts a quoter handed on as a value (`host_values`)
as well as a call, so `map(shell_quoted, w)` beside a branch on
`os.name` is seen.

Stacked on #724, whose final head 949fd4d is merged here and which has
landed as 2000488, merged here with main. #630, which rewrote the
README and handbook around this sentence, has landed too (190133d), and
so has #792 (#785, 5cbec22), which wrote into the same paragraph and
into `quote_arg`; main d7befb9 is merged here with both. The copied
`scripts/repo_shape.py` changes, so it rides in v1.1.1's shape bump.

Closes #711

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant