Repository navigation
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
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>
… 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>
|
(lane openRepoShape-5, 2026-10-17T10:56Z) @codex review |
Reviewer's GuideTests-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
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 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".
| 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) |
There was a problem hiding this comment.
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 👍 / 👎.
| 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)] |
There was a problem hiding this comment.
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 👍 / 👎.
|
(lane openRepoShape-2, 2026-10-17T16:15:46Z) Independent review of #808 (#759) at MUST 1: the branch conflicts with main in both test files.
SHOULD 1: the
NIT 1:
NIT 2: three
NIT 3:
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
NIT 6 (optional, for whichever of #808 and #821 lands second): #821's
Checked and true
Read by lane 2's coordinator from GitHub:
Could not verify
Lane: openreposhape-2 (openRepoShape-2) |
Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…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>
|
|
(lane openRepoShape-2, 2026-10-17T17:07:11Z) Re-verification of #808 (#759) at This is a delta from my review of MUST 1 (the conflict with main): FIXED.
SHOULD 1 (the strict
The NITs:
New in
Merges:
Checked and true:
Read by lane 2's coordinator from GitHub:
Could not verify:
Lane: openreposhape-2 (openRepoShape-2) |
|
(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
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 2 hours and 13 minutes by commenting |
… 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>
…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>
…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>



Lane: openreposhape-5 (openRepoShape-5)
Claim: #759 (comment)
Based on main. #724 landed as
20004887; this branch holds it and was restacked onto main as84ce67c9.Head
08a6ed39. The branch holds #759's commit052410b6, 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.pyandtests/test_validator_child_encoding.py. No shipped file changes, and no pinned copy. README, the handbook,AGENTS.mdanddocs/cli.mdare untouched, andrender-cli-reference.py --checkpasses.Fix round: lane 2's rv808 (2026-10-17),
08a6ed39on84ce67c9One commit, tests only:
tests/test_validator_child_encoding.pyandtests/test_readme_windows_quoting_sentence.py. No shipped file changes, so no pinned copy, README, handbook,AGENTS.mdordocs/cli.mdtext moves. Pushed tofix/759-quoting-launcher-guardsas a fast-forward of84ce67c9, 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.pyrow now flips on the fix it names, and the finding says six childrenWhat failed, measured. On
84ce67c9, with the realsetup-project.pychanged in a scratch clone, one change per row. The row istest_the_tools_that_run_the_projects_validators_give_them_an_environment[setup-project.py]:xfailedmeans it is still red,FAILED (XPASS strict)means it says the finding is fixed. Log:v116/f808/toolmut/.setup-project.py84ce67c9's guardenv=child_environment()inrun's ownsubprocess.run, which reaches all six childrenNOT_YET's reason stays falseenvparameter onrun, passed at all six callswhy_the_plan_did_not_go_throughleft outenv=at the five calls the old guard read, no parameterenv=at four of the callstest_setup_project_starts_six_children_and_the_guard_reads_eachfailsLane 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_throughruns the plan again,again = run(plan, cwd=shape_root, capture=True), andplanis a parameter, so the oldargv_ofcould not read the list.The sixth child, measured. I called the real
why_the_plan_did_not_go_throughwith a plan that prints aproject ...line holding an ellipsis and exits 2, under three encodings (v116/f808/probe6.txt):PYTHONIOENCODINGutf-8:strictproject Sampleand U+2026the plan was refused (see above).cp1252:strictproject Sampleand U+FFFDcp1252:surrogateescapeproject Sampleand U+FFFDSo 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_runsreads a list a caller hands a function as a parameter, one level up, and a name bound in each branch of anif. A starter's own parameter is left to its callers, which are the runs, sorun's ownsubprocess.runis not counted as a seventh. On the shipped files this changes exactly two reads (v116/f808/probe/survey.py):setup-project.pygains the sixth child (:2312on84ce67c9), andscripts/render-cli-reference.pygains:403(NIT 1).environment_ofreads, where a call hands noenv=, theenv=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_YETnames the six children, says five write into the reconfigured stdout and the sixth is captured, and names both fixes the row reads: anenv=inrun's ownsubprocess.run, or anenvparameter that all six calls pass.test_the_row_flips_on_the_change_the_finding_namesruns seven changes to a model ofsetup-project.py(the table's rows, on a source string).test_setup_project_starts_six_children_and_the_guard_reads_eachpins the six functions of the real file:check_names,clone_and_bootstrap,plan_and_confirm,scaffold,self_bootstrapandwhy_the_plan_did_not_go_through. While the row is anxfail, 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
argv_ofread only the first listSPELLINGShas the name bound to two lists in each order (therender-cli-reference.pyshape), andNOT_RUNShas a name bound to the interpreter only after the call.host_valuesbehaviours without a caseVALUE_USESgainsself.quote = shell_line,a = b = shell_quotedand a name read in an annotation; the value use in a method sits besidetest_a_method_is_named_with_its_class.environment_ofjoined every bindinginterpreter_runs' docstring lists them, as #821's guard lists "WHAT IT DOES NOT SEE", andSPELLINGSgainssubprocess.call,check_callandcheck_output. Stays unread: a tuple argv,[sys.executable, ...] + rest, a function-localexe = sys.executable,import subprocess as spandfrom subprocess import Popen,os.exec*. None is in shipped code, and each needs a resolver of its own.alias_offollowsq: object = shell_quotedas it followsq = shell_quoted, soq(x) + q(y)counts 2 and one switched to'posix'counts 1.names_posixreads a'posix'argument only after the value the quoter would quote, sofunctools.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, andNOT_FOLLOWEDpins what is counted instead (the statement, once for each quoter it names). None is in shipped code.calls_toreadshost_callonlyNIT 6, for whichever of #808 and #821 lands second. #821's
calls_tocountshost_call's calls and nothost_values' uses, so on the mergeskips_the_questiondoes not see a quoter handed on as a value. Measured on a scratch merge of08a6ed39and #821's head7581ffb9(v116/f808/nit6.txt):skips_the_question("scripts/x.py", ...)as merged' '.join(map(shell_quoted, w)) if os.name == 'nt' else ''indef a(w)set(){'a'}' '.join(shell_quoted(x) for x in w) if os.name == 'nt' else ''indef 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_valuesnames functions ascalls_todoes (<module>,Class.method). With it,tests/test_readme_windows_quoting_sentence.pyon 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), throughrun9.sh(gh stub first on PATH, bogus token, emptyGH_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.py82 to 111 cases (29 new),test_readme_windows_quoting_sentence.py28 to 40 (12 new), the other three 147 together. On84ce67c9the two files gave 109 passed, 1 xfailed (v116/f808/base-head.log). No other test file imports or drives either one:git grepfinds no use ofinterpreter_runs,environment_of,LAUNCHERS,host_calls,host_quotingorquoter_namesoutside these two files. On the scratch merge with #821 (4386e5fe, not pushed), these two files andtest_git_bash_dialect.py,test_family_printed_quoting.pyandtest_pinned_copy_text.pygive 417 passed, 1 xfailed (v116/f808/merge821.log).The new tests on
84ce67c9I put the new test files over
84ce67c9with 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 aNameError): 19 of the 41 new tests fail, 131 pass, 1 xfails (v116/f808/basefail-3.log). The failures: 7 changes to the model ofsetup-project.pyand the six-children pin; 3SPELLINGS(a list a caller hands a parameter, by position and by keyword, and a name bound to two lists with the interpreter second); 5ENVIRONMENTS(the last binding, three ways, and the environmentrunhands, two ways); and in the quoting file thepartial(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).failedcounts the tests that fail, among the launcher guard's own tests for a mutant oftests/test_validator_child_encoding.pyand in the whole file for one oftests/test_readme_windows_quoting_sentence.py. All 32 are caught.bound_values, above the callenvironment_of, last bindingargument_forlists_of, callerslists_of, held listsinterpreter_runs, any listown_environment_ofenv=loses to it / recurses into a starter that calls itselfNOT_YETrowLAUNCHERSvalue"", the strictxfailfires)alias_ofnames_posixhost_values, alias statementfunction_ofClassDefdroppedSTARTERSsubprocess.call/check_call/check_outputdroppedSix 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) andfunction_of'sClassDef. On the new files each is caught, as the table says.Merge checks (
git merge-tree --write-tree HEAD <ref>, main at64ec47c3)MSYSTEMset) the lines that follow the host are quoted forsh, through one question,host_shell_dialect, so the doctor's lines, setup's family lines andfamily.py'sNEXT,switch -c,addandcdlines paste whole in the shell that printed them, and README's Windows sentence says so #821 (fix/711-git-bash-dialect,7581ffb9): clean, and the only other head that changestests/test_readme_windows_quoting_sentence.py. This branch alone changestests/test_validator_child_encoding.py.fix/*andl3-*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-wipandl3-678-naming-checkout-wip. Each conflicts the same way with origin/main and with84ce67c9, 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.
What changed
1. The inventory sees a quoter handed on as a value, and counts calls.
tests/test_readme_windows_quoting_sentence.py:host_callsalso 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_quotingkeys by function), in source order.'posix'issh's, as for a direct call:functools.partial(shell_line, platform="posix")is not a use.NAMEDmaps each function to its number of host-dialect uses (_add_command3,_print_next_steps2,family_next_command2, 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_runsreads a call tosubprocess.runand its kin, to the package'srun_child, and torun(setup-project.py's wrapper), whose argv is[sys.executable, ...],[sys.executable or PYTHON, ...], or a list starting with a name the module binds tosys.executable(PYTHON = sys.executable). The list may be spelled out, held in a local name (plan = [PYTHON, ...], once or in each branch of anif), 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. APYTHONimported fromrepo_shapeispython3as a person types it, and is not read as the interpreter.environment_ofreads anenv=keyword, orrun_child's second argument, through a local name (cli.py'sscaffoldbuildsenvand adds a key before it passes it on). Where the call hands none, it reads theenv=of the function it calls when the module defines that function (run). A local name is read as its LAST binding above the call.LAUNCHERSgetssrc/openreposhape/cli.py: its fourrun_childcalls (:436,:462,:474,:564) each handstandard.child_environment(), which is what the file's docstring promises ("EVERY CHILD GETSstandard.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.LAUNCHERSgetssetup-project.py: its six Python children hand no environment, sincerunhas no parameter for one: fiverun([PYTHON, ...])calls (inself_bootstrap,check_names,plan_and_confirm,scaffoldandclone_and_bootstrap) and the sixth, inwhy_the_plan_did_not_go_through, which runs the plan again withplana parameter. That row fails, and it is the finding below.The finding:
setup-project.pywrites in two encodingsMeasured, not read.
PYTHONIOENCODING=cp1252:strictandcp1252:surrogateescapeare 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 theghstub first on PATH:PYTHONIOENCODINGcp1252:strictcp1252:surrogateescapeutf-8:strict(control)setup-project.pyreconfigures 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'sfamily ...rows with their em dash (3 lines), the scaffold'sshape ...andspec/codelines with their ellipsis (4), and the validatorsscripts/bootstrap.pyruns (2). Nothing raises, becausescaffold-project.pyandscripts/bootstrap.pyescape 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 strictxfail(NOT_YET): it passes while the row fails, and it fails the day the row passes, so whoever givesrunan environment has to take the row out of the table. The mark namesAssertionError, so any other error in the guard still fails. The fix is an environment from a copy ofinherited_child_environment(readingsys.stdout.encodingafter the reconfigure, soutf-8:replace), given either as anenv=inrun's ownsubprocess.runor as anenvparameter 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.pycannot import the helper: it runs before there is a checkout to import from.Decisions
funcof a call and not the alias statement itself (joined = shell_line, the assignmentquoter_namesfollows). That covers the issue's two forms and also a list entry, a default, a return value andself.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 ofjoinedis, and counting it would make a module-level alias a<module>row thatNAMEDwould have to name.'posix'is not a use.host_valueshas its own smallnames_posixrather than reusinghost_call's inline test.host_callis left as it is, since 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 (the Git Bash dialect) edits the rule that decides what forcessh.NAMEDchanges only in its values. 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 changestests/test_readme_windows_quoting_sentence.pytoo, so these hunks are additive, and none of them is at the end of the file.subprocess.run/Popen/call/check_call/check_output,runandrun_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 inSTARTERS, andtest_every_spelling_of_a_child_of_the_interpreter_is_a_runis where it is shown.Evidence
On the shipped tools nothing changes. The new
host_callsreturns what the old one did on every filegit ls-fileslists (host_quotingcompared across both: identical), so no host-dialect line escaped the sentence before, as the issue says.NAMED's counts match today's code:_add_command3,_print_next_steps2,_switch_command,_print_nextand_checked_mount_path1,family_next_command2.The tool mutants, each run against
151a0861's own two test files and then against these. Logs underv115/w759/mut/.def _w759_mutant(words): return list(map(shell_quoted, words))added toupdate-shape.pytest_the_tools_that_quote_for_the_host_are_the_ones_the_sentence_namesfails:update-shape.pyis among the filesshell_quoted(org, 'posix')in_add_command(one of three)test_the_functions_that_quote_for_the_host_...[scripts/family.py]fails:_add_command2, the table 3envdropped from one ofcli.py's fourrun_childcalls (:436,:462,:474,:564), one mutant each[src/openreposhape/cli.py]row fails, naming the line:src/openreposhape/cli.py:474 runs the interpreter without `env=standard.child_environment()`env = dict(os.environ)forscaffold'senvAgainst 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 (aNameErrorwhere 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 twosubprocess.runspellings the guard already read. Thecli.pyandsetup-project.pyrows have no base row to run. Logsv115/w759/basefail-quote.logandbasefail-enc.log.One mutant per new hunk, applied to the new test files only, each killed (
failedcounts the tests that fail):host_callsaddshost_valueshost_values,names_posixcall'posix'still a usehost_values, keywordast.keywordnot climbedhost_values, alias statementnames_posixkeywordplatform=/posix=ignorednames_posixargument'posix'argument ignoredfunction_of<module>host_use_countsSTARTERSrun_childleft outSTARTERSrunleft outinterpreter_namessys.executablenot followedargv_of(nowlists_ofandargv_lists; the fix round's own mutants table covers them)environment_of,run_childenvironment_of, local nameNOT_YETxfailmark dropped (the row is red)NOT_YETLAUNCHERSvalue""): the strictxfailfailsTests
These counts are
052410b6's. At08a6ed39they 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_TOKENbogus, emptyGH_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.py9 to 28 cases (19 new),test_validator_child_encoding.py56 to 82 (26 new, one of them thexfailrow), the other three 147 together. No other test file imports or drives what changed:git grepfinds no use ofinterpreter_runs,LAUNCHERS,host_callsorhost_quotingoutside these two files.Merge checks (
git merge-tree --write-tree HEAD <ref>)64ec47c3: clean. render-cli-reference.py and tests/: the CLI reference's children write 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 (#665) #724 landed as20004887, and nothing is stacked. The fix round's merge checks above are at08a6ed39; the rest of this section is052410b6's.fix/*andl3-*head on origin (47): clean, and none other than render-cli-reference.py and tests/: the CLI reference's children write 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 (#665) #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.fix/757tofix/777;fix/760is on origin): clean.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 (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 changesNAMEDand this file; whichever lands second re-gates against the other.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.Left out, and why
setup-project.pyrow is the finding, held as a strictxfail; fixingrunis a separate change.getattr(repo_shape, "shell_line")andglobals()["shell_line"]hand a quoter on by name at run time. The inventory reads names in the source.LAUNCHERS. A survey with the newinterpreter_runsover 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(passesenv=child_env(), found once NIT 1 was fixed),templates/assembly-root/scripts/bootstrap.py:148andtemplates/family-root/scripts/siblings.py:567(neither passes an environment; see below).Checklist candidates
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 localsetup-project.pyrun, thenproject.yamlname: "日本"),PYTHONIOENCODING=cp1252:strict python3 scripts/bootstrap.py:validate-manifest.pyraisesUnicodeEncodeErroratprint(f"manifest ok: {manifest.get('name')} ...")(:972) and bootstrap endsbootstrap FAILED: manifest (exit 1), exit 1. Withutf-8:strict:manifest ok: 日本 (sample), 3 legs, exit 0. It is bump-leg.py (a real bump) and update-shape.pyapplyrun the project's own validators as children that cannot print its display name on a cp1252 stdout, so a project named日本is refused and rolled back asbump-leg-validators-redorupdate-validators-redunderPYTHONIOENCODING=cp1252:surrogateescape, and the refusal blames the validators #254's defect in a pinned copy, so a fix is a shape bump, andmake bootstrapandsetup-project.pystep (7) both run it.setup-project.py'srungives its children no environment (the finding above). If the coordinator files it, theNOT_YETentry names the fix and the measurement.templates/family-root/scripts/siblings.py:567starts each member's ownscripts/bootstrap.pywith 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.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:
Tests: