Repository navigation
scaffold-project.py: a leg path that is, holds or lies inside a path the shape writes into the assembly root, a --pin's contracts/<product>-pin.yaml included, is refused before anything is created, under --dry-run alike, through the one definition adopt-project.py asks, so --spec-path contracts no longer creates three repositories and pushes both legs to die at git submodule add (#197) - #234
Conversation
…e moves out of adopt-project.py to sit beside `assembly_root_paths`, so scaffold-project.py can ask the same question from one definition, and adopt's sentences stay byte for byte what they were (#197) `scaffold-project.py --spec-path contracts` creates all three repositories, pushes both legs and only then dies at `git submodule add`, because the shape has already written `contracts/` into the assembly root (#197). The question that catches it is #172's shape half, which #198 wrote inside `adopt-project.py`, where the scaffold cannot import it. A second copy in the scaffold is how the two tools would start refusing different paths, so this commit moves the code and changes no behaviour. What moved, with #198's docstrings kept: `CASE_ASIDE`, `MOVE_THE_LEG`, `_overlap` (now `path_overlap`), `_first_and_more` (now `first_and_more`), and the half of `_shape_collisions` that reads `assembly_root_paths` (now `shape_path_collisions`). The functions are public because two modules import them. `shape_path_collisions` takes the `collision_dir` the caller hands `materialize_assembly_root`, so the two callers differ here by the argument they differ by there. Adopt passes its `COLLISION_DIR`, and its sentence, which names `execute` and `shape/`, is produced unchanged. What stayed in adopt-project.py: - `_shape_collisions`, which now asks the shared half and then its own question about `shape/`. That half reads #198's `SHAPE_DIR_RESERVED`, built from adopt's `COLLISION_DIR` and also said by `LEG_COLLISION_REMEDIATION`. Only an adoption writes copies there, and moving the sentence would have meant a second spelling of the phrase or moving a constant adopt's remediation reads. So it stays, verbatim. - `leg_path_collisions`, unchanged. - The root-kept half (`_kept_collisions`, `_kept_in_root`, `_leg_collision_findings`), since only an adoption reads a source tree. - `LEG_COLLISION_REMEDIATION`, which speaks of `check` and `shape/`. The moved names come in through one separate import statement, under the private names `_kept_collisions` and `_shape_collisions` already call. The statement sits after `import shape_advisory`, away from the materializer import list that #198 and #179 each edit. Checked: for every path `assembly_root_paths()` lists plus 24 other values (the `shape/` variants among them), asked as both legs, plus seven root-kept cases, the remediation and `SHAPE_DIR_RESERVED`, adopt's output is identical before and after (78 lines, `cmp` against #198's head 163173e, whose tree main's squash 20e13f0 holds byte for byte). tests/test_adopt_leg_path_collisions.py is unchanged and passes, with test_adopt_leg_paths.py and test_shape_doctor.py (622 passed). Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the shape writes into the assembly root is refused before anything is created, under `--dry-run` alike, so `--spec-path contracts` no longer creates three repositories and pushes both legs to die at `git submodule add` (#197) `_build_assembly_root` writes the shape into the root FIRST and mounts the legs after it, so a leg path the shape had just written met only `git submodule add`. Measured on #198's head 163173e, whose tree main holds byte for byte since its squash 20e13f0, against a local remote directory: - `--spec-path contracts`, `--code-path project.yaml` and `--code-path Makefile/code` each planned under `--dry-run` with exit 0. - The real run then created all three bare repositories, pushed a commit to each leg, left the assembly root with no commit and exited 2 (`'contracts' already exists and is not a valid git repo`, `destination path ... project.yaml already exists`, `could not create leading directories of ... Makefile/code`). - `--spec-path Contracts` exited 0 with a gitlink beside the shape's `contracts/`, which a macOS or Windows disk keeps as one path. Against GitHub, each left three real repositories for a run that made no project. THE QUESTION IS ADOPT'S (#172), through the one definition the previous commit moved: `shape_path_collisions` over `assembly_root_paths()`, with `path_overlap`'s relation (is, contains, is inside) and `CASE_ASIDE`. A file the shape gains is refused here without anybody editing this tool. The refusal is `scaffold-leg-path-collides`, beside adopt's `adopt-leg-path-collides`. It names each leg's overlap in #198's words, then a remediation that lists the materializer's paths and says nothing was created. `shape_path_collisions` gains `collision_dir=None`, the scaffold's case. The scaffold hands `materialize_assembly_root` no collision directory, so the sentence says what happens here instead of what adopt's `execute` would do: the shape writes the path into the root before the legs are mounted, and Git cannot mount a leg at a path the root already holds, or beyond a file it holds on the way there. Adopt passes `shape`, and its 78 dumped lines are still byte-identical to 163173e's, which are main's. WHY `shape` IS NOT REFUSED HERE. Adopt refuses a mount at or under `shape/` because `execute` writes the shape's copy of a file the source already holds there, and #198 reserves the directory for those copies. The scaffold materializes into a directory it made itself, where a collision raises rather than landing under `shape/`. `update-shape.py` reads `shape/` only for a pin that names a path under it, which only an adoption writes. So nothing the scaffold or its project's tools write lands there, and refusing it would be a rule with no collision behind it. `shape` and `shape/spec` scaffold, and a test holds that. WHERE THE REFUSAL SITS. `_scaffold` calls `_refuse_colliding_leg_paths` straight after the last `checked_value`, before any process can start: - before `_identity`, which may read git's config; - before `_declaration`, which asks `gh api` for a `--pin` with no `--pin-source`; - before `_leg_visibility`, which asks `gh repo view` about a reused root; - before the plan is printed and the `--dry-run` return; - before `mkdtemp` or `--work-dir`, the local remote directory and `_create_remotes`. A dry run therefore refuses with the real run's exit code and its exact stderr. `main()` does nothing but parse arguments before `_scaffold`. tests/test_scaffold_leg_path_collisions.py (89 tests): - Every path `assembly_root_paths()` lists is refused as either leg. So are a directory that holds them (`contracts`, `scripts`, `.github`, `.github/workflows`) and a path beyond a file (`Makefile/code`, `contracts/shape-pin.yaml/spec`). The case-only variants say the aside. - The refused files are exactly what `materialize_assembly_root` writes into an empty directory, and each directory written into is refused too. - Both tools hold `shape_materialize`'s own objects, and a spy shows both call them, adopt with `shape` and the scaffold with none. - `--dry-run` and the real run give the same exit code and the same stderr. - The real run leaves the remote directory's listing unchanged and makes no temporary work directory. - A stub `gh` records no call before the refusal, against a control that reaches it. - A non-colliding pair (`project`, `scripts/code`) still scaffolds. Most cases run `main()` in-process with `subprocess.Popen` refused, so a pass proves no git or gh process started. Out of scope, left: `--pin` also writes `contracts/<product>-pin.yaml`, which `assembly_root_paths()` does not list (`contracts` is refused through the other files under it, a pin's exact path is not). Non-canonical spellings such as `contracts/` and `./contracts` slip past this check until #179 (#201) lands its canonical check before `checked_value`. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-pin.yaml` a `--pin` writes is refused before anything is created, from the one `neutral_pin_path` the materializer writes it at, so `--pin openGlass@<sha> --spec-path contracts/openglass-pin.yaml` no longer plans and then makes three repositories (#197) #197's review of aabfae0 (S1) found the one path the materializer writes that the refusal did not know. `_build_assembly_root` hands `materialize_assembly_root` the declared pins and `_materialize` writes each at `contracts/<product lowercased>-pin.yaml`, but `assembly_root_paths()` described a call with no pin. On aabfae0, and on main a5b09c3, `--pin openGlass@<sha> --pin-source <clone> --spec-path contracts/openglass-pin.yaml` planned under `--dry-run` with exit 0, and the real run created all three repositories, pushed both legs and died at `git submodule add`. `Contracts/OpenGlass-pin.yaml` exited 0 with a gitlink beside the pin, one path on a macOS or Windows disk. ONE DEFINITION. `neutral_pin_path(product)` in scripts/shape_materialize.py is now the tools' only spelling of that path: `_materialize` writes the pin there, the scaffold's plan line and `descendant_note` name it from there, and `assembly_root_paths(neutral_pins=())` lists it between the copies and `contracts/shape-pin.yaml`, where the materializer writes it. `shape_path_collisions` takes the same keyword and asks about that list, and the scaffold passes its `--pin` products. A parameter on the list, rather than the scaffold appending pin paths to it, keeps "every path this call writes, in the order it writes them" in the one function beside the materializer, so the collision set, the remediation's list and the test that holds the list to what was written read one answer. The products come from `declared_pin_products`: `parse_pin` alone, the parser `declared_pin_values` uses, keyed and ordered as its dict is, with no digest and so no process. Adopt hands the materializer no pin and passes none, so its sentences are byte for byte what they were: the review's dump of adopt's collision sentences, re-run on these bytes, has sha256 5eb70cfb684a, the same as aabfae0's, and tests/test_adopt_leg_path_collisions.py is unchanged. The refusal runs where it did: after `checked_value`, before `_identity`, `_declaration`, `_shape_revision`, `_leg_visibility`, the dry-run return, the work directory, the remotes and `git init`. A malformed `--pin` is now refused there, by `parse_pin`'s own `pin-malformed`, rather than in `_declaration`; it was refused before anything was created either way. The remediation lists the pins' files with the rest. With no `--pin`, `contracts/openglass-pin.yaml` lies inside a directory the shape writes into, not a path it writes, and it still plans. Two ordering claims are now tests that run on every OS (review N3). A run without `--elected-by`, with GIT_AUTHOR_NAME unset because CI sets it, refuses with no process started, where its control starts `git config user.name`. With `_declaration` replaced by a recorder, a colliding `--pin` run never calls it, where its control does. Adopt's sentence tail, "`execute` would write it into the mounted leg", is pinned word for word. The `shape_path_collisions` docstring now says adopt passes its `COLLISION_DIR`, `shape` with no slash, and that `plan`, `check` and `execute` all reach it through `leg_path_collisions` (N4). Mutants, each against tests/test_scaffold_leg_path_collisions.py (104 tests): the scaffold passing no pins, 11 failed; `assembly_root_paths` dropping the pins, 10 failed; `neutral_pin_path` not lowercasing, 9 failed; the refusal moved after `_identity`, 1 failed, the new no-elector test alone; moved after `_declaration`, 13 failed, the new recorder test among them. Runs on these bytes: test_scaffold_leg_path_collisions, test_adopt_leg_path_collisions and test_scaffold_pin_and_reuse, 216 passed. On their merge with main a5b09c3, those three plus test_scaffold_e2e, test_scaffold_ignored_shape_files, test_adopt_leg_paths, test_shape_doctor, test_cli_reference, test_commit_trailers, test_adopt_mount_ignored and test_scaffold_leg_paths, 999 passed. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
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 days and 23 hours by commenting |
|
@codex review |
Reviewer's GuideScaffold now rejects any leg path that equals, contains, or lies within an assembly-root path written by the shape—including per-pin contract files—before starting any process or creating anything, with collision logic centralized and shared with adopt-project.py. Sequence diagram for early scaffold leg-path collision refusalsequenceDiagram
participant User
participant Scaffold as scaffold-project.py
participant Parser as parse_pin
participant Rules as shape_materialize.py
participant System as Processes and filesystem
User->>Scaffold: _scaffold(args)
Scaffold->>Scaffold: checked_value(--spec-path, --code-path)
Scaffold->>Parser: parse_pin(raw, pin_owner)
Parser-->>Scaffold: declared pin products
Scaffold->>Rules: shape_path_collisions(--spec-path, value, neutral_pins)
Rules->>Rules: assembly_root_paths(neutral_pins)
Rules-->>Scaffold: collision sentences or none
alt collision found
Scaffold-->>User: Refusal scaffold-leg-path-collides, exit 2
Note over System: No process, work root, remote, gh, or git is started
else no collision
Scaffold->>System: Continue with _identity and project creation
end
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
|
(lane openRepoShape-5, 2026-10-11T19:43Z) Independent review of #197 for lane openRepoShape-4, as offered. VERDICT (aabfae0; delta to 87fd767 assessed): APPROVE WITH NITS. Nothing blocking or should-grade is open at 87fd767.
Verified at 87fd767:
Carried from aabfae0 (not re-run):
#235 appears to cover the Confirmed follow-up, not a finding against this PR: a The full report is in |
|
(lane openRepoShape-4, 2026-10-11T20:03:46Z) The follow-up the re-review asked for before this lands is filed, re-verified on |
…ce` changes in git 2.43 (a module store reactivated, a gitlink accepted, an early dry run skipped and its refusal made later) and names the assembly directory `_refuse_existing_trees` checks, and the test module's comments hold before and after #197 lands (#229) Comments and docstrings only. The AST of `scaffold-project.py` and of `tests/test_scaffold_mount_ignored.py`, with docstrings removed, is identical to 9bb8bd0's, and `tests/test_scaffold_mount_ignored.py`, `tests/test_scaffold_e2e.py` and `tests/test_cli_reference.py` pass as they did. This answers the four nits of the re-check of 9bb8bd0: NIT-2. The comment in `_build_assembly_root` and the test module's docstring said `--force` "turns off three other refusals". Probed one by one on git 2.43.0 in a scratch repository (`GIT_CONFIG_GLOBAL` pointed at a file of its own, `GIT_CONFIG_NOSYSTEM=1`), two of the three are not turned off: - A `.git/modules/<path>` already there: exit 128 "A git directory for 'spec' is found locally with remote(s)" without `--force`; with it, exit 0 "Reactivating local git directory", and the gitlink is the store's commit, not the leg's. Turned off, as the comment said. - An entry at the path in the index: without `--force`, exit 128 "'spec' already exists in the index" for a gitlink, a regular file and a tracked directory alike. With it, a GITLINK is accepted (the leg is cloned and the entry is replaced, or, with a repository already checked out there, "Adding existing repo at 'spec' to the index" and the entry keeps that repository's commit), and a regular file or a tracked directory is still refused, as "already exists in the index and is not a submodule". - git's early dry run of the path, which refuses a path beyond a symlink ("pathspec 'link/sub' is beyond a symbolic link") or inside a submodule ("Pathspec 'spec/sub' is in submodule 'spec'") before anything is cloned: with `--force` it is skipped, and git still exits 128 on both, later. Beyond a symlink it first CLONES the leg into the link's target and fails at the last step ("Failed to add submodule"); inside a submodule it refuses to create a git directory inside the other's. The refusal is moved, not turned off. The comment now says each of these as probed. The conclusion stands: none of the three can arise in `_build_assembly_root` (the root is `git init`ed with `--template=`, the index holds only `.gitmodules` and the first gitlink at the second mount, and the materializer writes only regular files), and the paragraph on why `--template=` closes the planted-store case is unchanged. NIT-3. The comment said "The work directory was empty (`_refuse_existing_trees`)". That function refuses a `<work-dir>/<assembly>` that exists and is not empty, and says nothing about the work directory, which may hold anything. The comment now says that, and draws the same conclusion from it: no `.git` was inside the assembly directory when the run began. NIT-1. The `SHAPE_PATHS` comment said git refuses those paths "at the mount (exit 128)". That is true on main, and false once #197 (PR #234) lands, when they are refused up front as `scaffold-leg-path-collides`. The comment now says both ("where #197's collision check is in force ... where it is not"), without claiming which state it is in, and that the tests assert only exit 2 and no gitlink, so they hold in both. It mirrors the comment in `_build_assembly_root`. NIT-5. The clone test's docstring quoted `Fetched in submodule path 'spec'`; under the mutation (`--template=` removed) git names `'code'`, because the template plants both stores. The docstring now says the path is a leg's, and names both. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…) into fix/plan-report-stdout-encoding (#214) One conflict, resolved by keeping both sides: the `from shape_materialize import (...)` list in update-shape.py. This branch added `escape_what_stdout_cannot_encode` and main added `force_stage` (#202) in the same place. Both names are now in the list, in its existing order (commit_trailers, escape_what_stdout_cannot_encode, force_stage, lane_trailer_argument, ...), wrapped to 79 columns like its neighbours, so the list's tail lines re-flow with it. No other file conflicted, and nothing else was changed. Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… `opensoft/openGlass` "by default" since `--pin-owner` changes it, its docstrings say "On main through 39ac59c" now that "as on main" is no longer true, and a pin's file is compared with the shape's files case-folded on both sides, as the leg-path check does (#241) Fix round 1 for #241's review (a25c2e1, MERGEABLE WITH NITS); the merge of origin/main (S1) is the commit beneath this one, and its changed lines are exactly #241's own, `git diff 87fd767 a25c2e1`. N1: `PIN_COLLISION_REMEDIATION` said `--pin openGlass@<40 hex commit>` "names opensoft/openGlass" as a fixed fact. Under `--pin-owner MedxSoft` it names MedxSoft/openGlass, so the sentence was false for that run. It now says "by default", as the neighbouring `pin-malformed` remediation does ("resolved under opensoft/openGlass by default"). The test's `REMEDIATION` constant, which asserts the message word for word, says the same. N2: the `_refuse_colliding_pins` docstring and the test module docstring said "On #197's 87fd767, as on main" (and "On main and on #197's 87fd767"), which stops being true once this lands: main then refuses what they describe. They say "On main through 39ac59c" now, the file's own style (`_refuse_colliding_leg_paths`: "On main through 20e13f0"). The test docstring of `test_a_dry_run_refuses_exactly_as_the_real_run`, "On main the dry run said yes", gets the same words, being the same sentence. N3: `_refuse_colliding_pins` asked `path not in shape_files`, exact, while #234's `path_overlap` compares the leg paths case-folded for the case- folding disk of macOS and Windows. Both sides are `casefold()`ed now, with a comment saying why: a future mixed-case file the shape writes under `contracts/` would otherwise be passed by a pin whose lowercased path collides with it only on that disk, and die in `place()`. The change is EQUIVALENT TODAY and no test is added for it: `neutral_pin_path` lowercases, and every `contracts/*-pin.yaml` in `assembly_root_paths()` (`spec-pin`, `code-pin`, `shape-pin`) is lowercase, so the review's mutant that case-folds this comparison passes every test and no test could tell the two apart. N4 and O1-O3 of the review are follow-up issues, not this change. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ng-paths main moved to 39ac59c: #197 (b538015, PR #234) moved `_overlap` and `_first_and_more` into scripts/shape_materialize.py as `path_overlap` and `first_and_more`, and adopt-project.py imports them back under the old names; 150c41d and 39ac59c (PRs #233 and #235) touch update-shape.py and scaffold-project.py. One conflict, in adopt-project.py: this branch had edited `_first_and_more` to `_spelled` its path, and main removed the function. Resolved by taking main's side of that one hunk: `_overlap` and `_first_and_more` are deleted from adopt-project.py, and main's import of the shared definitions stays as it is. No line is written by hand in this merge. It leaves `_kept_collisions` naming a source path through `first_and_more`, unspelled, so the spelling case of tests/test_adopt_finding_spelling.py fails on this commit alone; the next commit gives `first_and_more` an optional `spell` and passes `_spelled`. Lane: openreposhape-3 (openRepoShape-3) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…) into fix/adopt-record-submodule-placement Brings in b538015 (#197), 150c41d (#202) and 39ac59c (#229) on top of 4835fb4. No rebase. They landed while this branch's targeted run was going, and the files they change overlap this branch's: adopt-project.py and scripts/shape_materialize.py. No conflicts. Checked: - adopt-project.py: main moves `_overlap` and `_first_and_more` into shape_materialize.py as `path_overlap` and `first_and_more`; this branch's `submodule_record` and its record append are untouched. - scripts/shape_materialize.py: main adds `shape_path_collisions`, `force_stage` and `neutral_pin_path`, and `assembly_root_paths` takes `neutral_pins`. This branch's `_spelling_reason` and `LEG_SEGMENT_REASONS`, which ask `repo_shape.path_spelling_fault`, are untouched, and no line main added names either, nor `leg_path_problem` (`git diff 4835fb4 origin/main`). - The branch's diff against main is still #189 alone: 12 files, +1670 -156, the same as against 4835fb4. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e-notice It merged with no conflict. Main brings six commits since 54aa69a: - e7d1720, update-shape.py (#208 via #224); - 404760f, scripts/repo_shape.py's double-quoted flow items (#213 via #244), with tests/test_yaml_flow_quotes.py; - 4835fb4, adopt-project.py's plan writer escapes (#199 via #247), with tests/test_adopt_plan_escapes.py; - b538015, scaffold-project.py's leg paths against the shape's own paths (#197 via #234); - 150c41d, update-shape.py's `apply --add` (#202 via #233); - 39ac59c, scaffold-project.py's leg mount `--force` (#229 via #235). Two of them touch adopt-project.py, and no hunk of either meets this branch's: - 4835fb4 changes the plan writer (`y`, `_PLAIN_RE`), adds `_shown` to the plan and check reports, and writes the `--paths-from-file` listing in `_extract_leg` through `write_paths_listing`. This branch changes other lines of `_extract_leg`: it reads the tree digest before the push and pushes through `_push_the_leg`. The signature is the same on both sides, so main's new direct `_extract_leg` test calls it as written. - b538015 moves `_overlap`, `_first_and_more`, `MOVE_THE_LEG` and `CASE_ASIDE` into scripts/shape_materialize.py, and imports the first three back under the same names. This branch adds no use of any of them. A `symtable` check finds no name that the merged adopt-project.py, scaffold-project.py or scripts/shape_materialize.py reads and nothing binds. `git diff -U0` of this merge against 39ac59c equals the branch's against 54aa69a, apart from the hunk headers. On the merged tree, tests/test_adopt_plan_escapes.py, tests/test_yaml_flow_quotes.py, tests/test_scaffold_leg_path_collisions.py and tests/test_adopt_leg_path_collisions.py give 275 passed. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>



Lane: openreposhape-4 (openRepoShape-4)
Claim: #197 (comment)
Fixes #197.
What
scaffold-project.pynow refuses a leg path that is, holds or lies inside any path the shape writes into the assembly root. This includes each--pin'scontracts/<product>-pin.yaml. The refusal isREFUSED scaffold-leg-path-collides, exit 2. It runs before any process starts (no work directory, no remote, nogh, nogit), and--dry-runruns it too. It asks the same question adopt-project.py asks (#172, #198), from one definition:--spec-path contracts, so the adoption fails after both legs are pushed (main: at the first bootstrap) #172's collision rule out ofadopt-project.pyintoscripts/shape_materialize.py, besideassembly_root_paths:shape_path_collisions,path_overlap,first_and_more,CASE_ASIDEandMOVE_THE_LEG. Adopt imports them back under its old names. Its output is byte-identical over the collision cases (a 44523-byte dump of every case,cmpexit 0), andtests/test_adopt_leg_path_collisions.pyis unchanged._scaffold, straight after the leg paths'checked_valueand before_identity,_declaration'sgh api,_leg_visibility, the dry-run return and the work root. It also addstests/test_scaffold_leg_path_collisions.py.--pinhole.neutral_pin_path(product)is now the pin file's only spelling in the tools: the materializer writes the pin there, the plan names it there, andassembly_root_paths(neutral_pins=...)lists those files in the order they are written, so the refusal knows them. The commit also pins two ordering claims with tests that run on every OS: the refusal runs before_identity, and before_declaration, checked in-process. It also corrects one docstring.The relation words, the case aside and the remediation are adopt's own. A case-only variant (
Contracts,PROJECT.yaml) is refused with the case aside on every OS: the comparison is between strings, and on a case-insensitive disk the two are one path anyway.One ordering change comes with 87fd767: a malformed
--pinis now refused byparse_pin's ownpin-malformedbefore_identity, ahead of four refusals that used to come first (scaffold-leg-path-collides,scaffold-no-elector,election-date-malformed,scaffold-bad-id). Its text is byte-identical, and every case still refuses before anything is created.Non-canonical spellings of a colliding path (
contracts/,./contracts,Contracts/) and Git's own names such as.gitmodulesare refused first by #179's canonical-spelling check. That check landed onmainas 8df21e6 (#201) and runs before this one in_scaffold. This branch must land on amainthat holds 8df21e6, which a5b09c3 does, and on that merge every such spelling is refused before anything is created. aabfae0's commit body predates #201 landing and says they "slip past"; that sentence is stale.--helpis unchanged, sodocs/cli.mdis too.Why (repro on
main)On
main,--spec-path contracts,--code-path project.yaml, or a leg at a pin's file planned cleanly under--dry-run. The real run then created and pushed all three repositories and died atgit submodule add, leaving three real GitHub repositories for a run that made no project. A case-only variant of a pin's file (--spec-path Contracts/OpenGlass-pin.yaml --pin openGlass@…) exited 0 with a gitlink beside the filecontracts/openglass-pin.yaml, a project a macOS or Windows disk cannot check out.Tests
tests/test_scaffold_leg_path_collisions.py, 104 tests:assembly_root_paths()returns, as either leg, inside / holds / case variants;--pinpaths, including a second pin and the capitals variant, withcontracts/openglass-pin.yamlleft clear when there is no--pin;executesentence tail.maina5b09c3, the scaffold, adopt-collision, pin, e2e, ignored-files, leg-path, doctor, cli and trailer suites, with every test file scaffold-project.py: a leg's mount path must be canonical or it is refused before anything is created, so--spec-path spec/no longer scaffolds a project whose recursive clone fails once the work directory is gone (#179) #201 and adopt-project.py: the leg mount passes--forcetogit submodule add, so a source whose.gitignoreis*+!.gitignore, or namesspecorcode, adopts instead of dying at the mount after both leg remotes were pushed (#174) #200 changed, gave 999 passed.shape: all caught;_identityor after_declaration: caught by the new ordering tests.--pinpath, is 87fd767.macOS and Windows have not run these tests yet; this PR's CI is the first time.
Landing notes
main54aa69a (adopt-project.py: verification passes over the two leg mounts and no other gitlink, so the source's own submodule kept in the root isadopt-root-submodule, any other root gitlinkadopt-unexpected-gitlink, one in a leg AND the rootadopt-duplicated, and a missing mountadopt-mount-missing(#165) #173 landed after this head was pushed), and with adopt-project.py:executeprints the command and its stderr when a leg cannot be built, so agit filter-repothat fails after both leg remotes exist no longer exits 2 with an empty stderr (#171) #184. This PR's CI ran on that merge, green on all three OSes: 2095 passed on ubuntu and on macOS, 1859 passed (237 skipped) on Windows. Lane 1's scaffold-project.py: a leg's mount path must be canonical or it is refused before anything is created, so--spec-path spec/no longer scaffolds a project whose recursive clone fails once the work directory is gone (#179) #201 is onmain, and this head was tested on top of it._build_assembly_rootrunsgit submodule addwithout--force, so a leg path an ignore rule matches (--code-path __pycache__or.DS_Storeunder the shape's own.gitignore, or the defaultspecunder an operator's global excludes) passes--dry-run, then dies at the mount AFTER all three repositories exist and both legs are pushed #229 (this lane, filed today) will also touchscaffold-project.py, in_build_assembly_root's mount, a different function. Its PR will say whether the merge is mechanical.--pinwhose product names a file the shape already writes (--pin spec@…givescontracts/spec-pin.yaml), or two pins differing only by case, plans and then dies in the materializer after the repositories exist;.gitignoreignores, now scaffold-project.py_build_assembly_rootrunsgit submodule addwithout--force, so a leg path an ignore rule matches (--code-path __pycache__or.DS_Storeunder the shape's own.gitignore, or the defaultspecunder an operator's global excludes) passes--dry-run, then dies at the mount AFTER all three repositories exist and both legs are pushed #229.🤖 Generated with Claude Code