Repository navigation
setup-project.py: the clone path <into>/<Project> is refused in step (3), before anything is created, when anything at all stands there, naming the path and telling a rehearsal's leftover clone apart as one to move or delete, and the plan names the clone path before the yes, so rehearsing and then running for real in the same folder - README's own order - no longer creates three repositories and then refuses with a hand clone that fails on the same directory (#498) - #509
Merged
Merged
Conversation
…p (3), before anything is created, when anything at all stands there, naming the path and telling a rehearsal's leftover clone apart as one to move or delete, and the plan names the clone path before the yes, so rehearsing and then running for real in the same folder - README's own order - no longer creates three repositories and then refuses with a hand clone that fails on the same directory (#498) README's "Rehearse first, creating nothing" runs the scaffold against `--local-remote-dir ./rehearsal` and leaves `./Atlas` behind, "both of which you can delete". A person who kept it and ran the real command in the same folder went through the preflight, the naming check, the plan and the yes; step (6) created and pushed all three repositories; and only then did step (7) find `./Atlas` and refuse. Its printed `git clone` failed on the same directory, the `cd Atlas` after it entered the rehearsal's project, and a re-run was refused `scaffold-remote-exists`. The path was decided in step (3), by `resolve_family`, and nothing read it before step (7). - Step (3), right after `resolve_family`: `refuse_a_taken_clone_path` refuses when anything at all stands at the clone path. `os.path.lexists`, so a symbolic link to nothing counts: `exists` reads it as absent and `git clone` still refuses it. An EMPTY directory is refused too, though `git clone` would take one: #498's option 1, put to Brett Heap on 2026-10-13, who did not object. The refusal, exit 2, says "<path> already exists, and that is where this project would be cloned, so the run stops here and nothing was created.", then what is there where it can tell (`what_stands_at`), then the way out. - A rehearsal's clone is told apart by all three of: an `origin` on this disk, named `<Project>.git`, and a `project.yaml` at its root. "It is the clone a rehearsal left behind: its `origin` is <path>, a repository on this disk, as `--local-remote-dir` makes one. Move or delete it, then run this again." An empty directory is offered for deletion too. Anything else is somebody's and is never offered for deletion: a clone of GitHub (named, with any `user:token@` dropped), a local mirror under another name, a directory that is not a clone, a file. "Move it, or choose another --into, then run this again." - The `origin` is read with `git config --file <clone>/.git/config`, never `git -C <clone>`, which walks up from an empty or broken `.git` and reports the `origin` of whatever repository holds `<into>`. A value carrying a control character is not echoed. - Step (5): the plan prints `clone into <path>` after the scaffold's dry run, on every run, `--yes` and no terminal included. The plan named the three repositories and never the one thing the run writes on this disk; it is also what an assistant relays to the person deciding. - Step (7) keeps its check as the backstop for a path that appears while the run is going, now `lexists` too. Its way out moves the thing aside first and runs the hand clone from the clone's parent, where it used to print a `git clone` that met the same directory. `setup.sh` hands over to `setup-project.py`, so both entry points refuse alike. The runs through each are asserted once, in two helpers in `tests/conftest.py`, with a one-call wrapper per file. README.md, AGENTS.md and docs/handbook.html are #483's (lane 5) and are not changed here; no `--help` text changed, so docs/cli.md stands. 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 1 day and 7 hours by commenting |
Reviewer's GuideThe PR closes the rehearsal-leftover safety gap by determining and refusing an occupied clone path before any remote creation, identifying safe versus unsafe existing content, showing the destination in the confirmation plan, and preserving a clearer late-race backstop; comprehensive tests cover both entry points and the path-classification edge cases. Sequence diagram for preflight clone-path refusalsequenceDiagram
participant User
participant Main as _main
participant Resolver as resolve_family
participant Guard as refuse_a_taken_clone_path
participant Classifier as what_stands_at
participant Remotes as Remote creation
User->>Main: Run setup-project.py
Main->>Resolver: resolve_family(opts, shape_root)
Resolver-->>Main: landing
Main->>Main: landing.clone_path(opts.project)
Main->>Guard: refuse_a_taken_clone_path(clone, project)
alt clone path is occupied
Guard->>Classifier: what_stands_at(clone, project)
Classifier-->>Guard: description and disposal guidance
Guard-->>User: Refusal, nothing was created
else clone path is free
Guard-->>Main: Continue
Main->>Remotes: scaffold and create repositories
end
Sequence diagram for rehearsal clone detectionsequenceDiagram
participant Guard as refuse_a_taken_clone_path
participant Classifier as what_stands_at
participant Config as origin_recorded_in
participant Git as git config --file
Guard->>Classifier: what_stands_at(clone, project)
Classifier->>Config: origin_recorded_in(clone)
Config->>Git: git config --file clone/.git/config --get remote.origin.url
Git-->>Config: origin URL
Config-->>Classifier: origin URL or empty
alt local origin, Project.git, and project.yaml
Classifier-->>Guard: Rehearsal clone, disposable
Guard-->>Guard: Move or delete it
else other existing content
Classifier-->>Guard: Non-disposable description
Guard-->>Guard: Move it or choose another --into
end
Flow diagram for clone destination confirmationflowchart TD
A[resolve_family determines landing] --> B[Compute clone path]
B --> C{Path occupied?}
C -->|Yes| D[Classify existing content]
D --> E[Refuse before remote creation]
C -->|No| F[Run names and scaffold dry run]
F --> G[Print clone into path]
G --> H{Confirmation required?}
H -->|Yes| I[Ask for confirmation]
H -->|No| J[Create repositories and clone]
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
…ready stands at is refused up front, so the rehearsal's `./Atlas` is no longer optional to delete before a real run in the same folder, AGENTS.md names it as the third refusal an assistant can foresee before anything is created, and "What setup.sh does" no longer says the flow has no behaviour of its own (#498) The tool half of #498 refuses `<into>/<Project>` in step (3) when anything stands there. The documents still said otherwise. Brett Heap ruled at 2026-10-13T11:30Z that each lane-4 fix pull request carries its own doc lines, so they are corrected here, on the text #505 (af01068) left. Each file keeps its line count: README.md stays at its cap of 1422, AGENTS.md at 490, and docs/handbook.html at 1933. - README.md:306-313. The rehearsal "leaves behind ... all of which you can delete" now reads "all deletable; a real run here refuses up front while `./Atlas` is there". The rest of that paragraph is rewrapped with its words unchanged. - README.md:524-525. "What setup.sh does" no longer says the flow runs its commands "with no behaviour of its own". It says that before the second command the flow refuses, creating nothing, if anything stands at `<into>/Atlas`. - AGENTS.md:88-96. "Two you can foresee from your flags" becomes "Three you can foresee". The third is anything already at `<into>/<Project>`, a rehearsal's clone included. The assistant asks the human to move or delete it and never does either itself. Both refusal codes are still spelled as before. - AGENTS.md:110-113. The steps by hand begin with "a free clone path". - docs/handbook.html:869-872, 956-957 and 1818-1825 are the README's and AGENTS.md's twins of the above. The footer at 1872-1875 adds "a taken clone path refused up front, added (#498)" and sets the README blob to `git hash-object README.md`. README.md:445 ("clones the root as `./Atlas` where you ran it") is left as it is. Saying more there fits only by rewrapping all ten lines from 436 to 445, which belong to other findings. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…'s clone is said to LOOK like one and is offered for deletion only "if nothing in it is yours", a symbolic link there is named as one and never offered for deletion, and a credential is cut at the last `@`, so a real working clone of a local or network repository with unpushed work is no longer told "It is the clone a rehearsal left behind ... Move or delete it" (#498) The independent review of #509 at 427237a found this (MUST-1). The three signs `what_stands_at` reads are an `origin` on this disk, named `<Project>.git`, and a `project.yaml` at the root. A working clone of a repository on a local path, a NAS mount or a UNC share meets all three, and so does a rehearsal's clone somebody has since worked in. The refusal stated a guess as a fact and offered deletion outright, and an assistant relaying "delete it" could destroy unpushed work. - The rehearsal sentence now reads "It looks like the clone a rehearsal left behind: its `origin` is ..., a repository on this disk, as `--local-remote-dir` makes one." The way out is "Move it, or delete it if nothing in it is yours, then run this again." Only an empty directory still gets a plain "Move or delete it". - The `origin` is still read with `git config --file`, and no git runs inside a directory this tool did not make: its config can name a command (`core.fsmonitor`) that `git status` executes. That is why unpushed work is not checked for, and why the wording carries the condition instead. - `what_stands_at` returns its way out with its sentence, so the three strengths (`DISCARD_IT`, `DISCARD_IT_IF_UNUSED`, `KEEP_IT`) are chosen where the thing is identified. - A symbolic link at the clone path is named first: "It is a symbolic link to <target>.", with KEEP_IT. `os.path.isdir` follows a link, so a link to an empty directory used to be offered "Move or delete it", and `rm -r Sample/` acts on the target. That was NIT-2. - A directory whose `origin` cannot be read says "It is a directory holding a `.git`." rather than "It is a git repository.". An empty `.git`, or a `.git` file naming a missing gitdir, is not a repository (NIT-2). - `without_credentials` cuts up to the LAST `@` before the path, so `https://dana:p@SECRET3@github.com/...` no longer leaves `SECRET3` showing. It now applies to the rehearsal sentence too (NIT-1). Tests, in tests/test_setup_project_py.py, with conftest's rehearsal assertion following the new wording: - a working clone of a local bare repository setup never made, holding an unpushed commit, is told only that it looks like a rehearsal's clone, and never "Move or delete it" (MUST-1); - an `origin` carrying an ESC sequence and a newline before a forged `REFUSED:` line. It is written into `.git/config` as bytes and read back by git first, and is never echoed (SHOULD-1, which pins `CONTROL_RE`); - the step (7) backstop with a symbolic link to nothing at the path, which pins its `lexists` (SHOULD-2); - a password holding `@`, and a `file://` origin with credentials (NIT-1); - a link to nothing and a link to an empty directory, each named as a link with KEEP_IT (NIT-2). Each change reverted alone fails these tests. The old rehearsal wording fails 4. Dropping the control-character filter fails 1. The backstop asking `exists` fails 1. The first-`@` cut fails 1. Dropping the link branch fails 2. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
14 of 17 tasks
brettheap
pushed a commit
that referenced
this pull request
Oct 13, 2026
… footer keeps #509's clause "a taken clone path refused up front, added (#498)" and adds this pull request's after it, with `README.md blob` re-hashed from the merged README.md (#500) PR #509 landed as cdcd2f0 and rewrote the footer of docs/handbook.html: its "added (#498)" clause and the `README.md blob <hash>` line that tests/test_repo_hygiene.py checks. This branch rewrites the same lines, so the merge conflicted there and only there. The resolution keeps main's text, adds "the installed doctor keeping its clone of the standard, which its NEXT lines run from, added (#500)" in the same `; ... added (#NNN)` form before " — a page cannot name the commit", re-wraps only the lines it touches, and sets `README.md blob` to `git hash-object README.md` of the merged file: 43df8f4 on main, 6bf3f89 on this branch's head, 5f1868d once merged. README.md stays at 1422 lines and AGENTS.md at 490. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This was referenced Oct 13, 2026
brettheap
pushed a commit
that referenced
this pull request
Oct 13, 2026
…471's PR carries main's #509 and #435 and its footer reads right (#471) One conflict, docs/handbook.html's footer, which every PR in the doc train rewrites. Resolved by the doc-train rule: main's text is kept whole, including #509's clause "a taken clone path refused up front, added (#498)", and this PR's own clause, "the line-ending block an adoption appends to a root .gitattributes the source keeps, and its verification as the source's bytes plus that block, added (#471)", follows it in the same "; ... added (#NNN)" form before " — a page cannot name the commit". Only the touched lines were re-wrapped, to the footer's 100-column width. The footer's `README.md blob` moves from c61ac37 (this branch before the merge; main's own was 43df8f4) to e6c41c9, `git hash-object README.md` of the merged README.md. README.md stays at 1422 lines and AGENTS.md at 490. Every other file merged without a conflict, including adopt-project.py, which both sides changed (main's #435 shape checks on the plan, this PR's block). Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
brettheap
pushed a commit
that referenced
this pull request
Oct 13, 2026
…e: docs/handbook.html's footer keeps main's text and adds this PR's clause after #498's, and names the merged README.md's blob (#499) Five commits of main since this branch's base 47b1e06 merged, among them #509 (cdcd2f0, #498), which rewrote the handbook footer, and #435 (f391e58, #386), which gates a plan's `source:`, `legs:`, `allow_empty_legs:`, `paths:` and `pins:` by type at the top of `adopt-project.py`'s `check` and `execute`. AGENTS.md, README.md and adopt-project.py merged without a conflict. docs/handbook.html conflicted in its footer only. The resolution keeps main's text, "a taken clone path refused up front, added (#498)" included, adds this PR's clause after it in the same form, "an adoption’s <code>--project</code> as the source’s own name, added (#499)", and re-wraps only the two lines that clause touches, to the footer's 100 columns, now three. The PR's own body edit at "It KEEPS ITS NAME" merged as it was. `README.md blob` moves from main's 43df8f4 and this branch's d19633d to 045250e, `git hash-object README.md` of the merged README.md, which `tests/test_repo_hygiene.py` checks. README.md stays at 1422 lines and AGENTS.md at 490. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap
added a commit
that referenced
this pull request
Oct 13, 2026
…s a runnable `scripts/family.py`, printed commands quote paths, and `add`, `bump` and `remove` refuse the tracking branch and print their branch's push (#488, #486, #487) (#507) Three defects from lane 3's v1.0.0 acceptance pass of AGENTS.md's "Creating a family, and adding a member", one commit each, in this order, then one review commit for #486 and one for #487. All three change `scripts/family.py` and `add`'s NEXT line, so they are one pull request. **What was wrong.** - AGENTS.md spelled the command `python3 scripts/family.py init --org <org> --family <Name>`, a path that runs from the standard's root. `--into` defaults to where the person stands. - So the command, followed exactly, printed `landing <standard>/Orbit/Orbit` with no word, and `make siblings` would then clone every member into the standard's checkout too. - `init`'s NEXT STEPS printed `python3 family.py add …`, built from `Path(__file__).name`. From the root that fails `can't open file`, exit 2. **What changed** (commit 1). - With neither `--into` nor `--work-dir`, `init` refuses a landing inside a checkout of openRepoShape: `family-landing-in-standard`. A checkout is a directory holding `scripts/family.py` beside `templates/family-root/`, found in the landing's ancestry, so another clone or an install counts too. - The refusal comes before anything is created, under `--dry-run` alike. - The remedy names `--into ..` from the root. From deeper inside the checkout it names the relative path that reaches the checkout's parent. - An explicit `--into` or `--work-dir` is not second-guessed. `family-holder-already-here` is still asked first. - NEXT STEPS, and the `family-holder-already-here` remedy, name this file relative to the directory `init` ran in. From the root that is `scripts/family.py`, as setup-project.py, validate-family.py, AGENTS.md and README spell it. It is absolute where no relative path exists. - AGENTS.md's init command passes `--into ..`, as README's already does, and step 1 says why. The edit is line-neutral: step 1 is one line shorter. - `init --into --help` says it too, and docs/cli.md is regenerated. **How tested.** `tests/test_family_init_landing.py`, 9 tests: - the old command from the root is refused, as a dry run, so nothing can land in the checkout; - a real run inside a fake checkout creates nothing; - the remedy from a subdirectory names the right parent; - an explicit `--into .` or `--work-dir .` passes; - outside a checkout the default is unchanged; - AGENTS.md's line, as written, lands beside the checkout; - the printed NEXT runs, both from the root and from elsewhere. On origin/main code 6 of them fail. Removing the refusal turns 3 red, and putting `Path(__file__).name` back turns 2 red. **What was wrong.** `family.py` and the holder's `siblings.py` printed every path in their pasteable commands bare. Under a holder at `…/my work/Sonata/Sonata`: - the `member-git-dir-cached` remedy `rm -rf <store>` split into `rm -rf …/my`, an absolute directory that is NOT the store, and a relative `work/…`; - `init`'s NEXT STEPS and `add`'s NEXT split `--family-root` and `git -C` (`fatal: cannot change to '…/my'`, exit 128); - the `mv` lines for a misnamed family folder pasted as four arguments. **What changed** (commit 2, and the review commit after #487's). - `scripts/repo_shape.py` gains `shell_quoted(value, platform=None)` and `shell_line(words, platform=None)`, in a new section at its end, the library `family.py` already imports. - It is the doctor's `quote_arg` rule (#101/#102): the same two alphabets, and the same two escapes, `sh`'s `'\''` and PowerShell's doubled apostrophe. The dialect comes from `os.name` or a `platform` argument. An ordinary word is left bare. - It does not flatten to ASCII, as the doctor's report does by contract. A command that deletes or moves a directory has to name that directory. So PowerShell's typographic single quotes are doubled too. - A word that begins with `=` is quoted, in both dialects. zsh, macOS's login shell, expands a bare `=word` to a command's path (`=ls` reaches the command as `/bin/ls`). This is the one place it differs from the doctor's rule on ASCII. - `family.py`: - `init`'s NEXT STEPS print the `add` on ONE line. It used to paste only into a POSIX shell, because of its `\` continuation. `cd <holder>` and `make siblings` are two lines, with no `&&`, which Windows PowerShell 5.1 rejects between two statements. - Those lines and the `family-holder-already-here` remedy quote every path. - `add`'s NEXT quotes `git -C <holder>`, and `member-path-taken` quotes each spelling it names. - `member-git-dir-cached`, and `remove`'s note about the same store, print the directory alone on its own line. Then comes `rm -rf` with its one argument quoted for the POSIX shell `rm -rf` belongs to, and the sentence names that shell: Git Bash on Windows. PowerShell refuses `-rf` and deletes nothing. - `siblings.py`: the parent-folder warning's `mv` and `mkdir -p` lines are quoted for a POSIX shell with the standard library's `shlex.join`, and the warning says which shell. It does NOT import the new quoter: a holder that kept its own `repo_shape.py` through `update-shape.py apply --accept-local` takes the new `siblings.py` without it, and an import of a name its copy lacks stopped `make siblings`, `park` and `resume` with an ImportError that `check` and the validator never reported. - `setup-project.py`'s family hand-over quotes the holder in its `add` line (and names the branch first, see #487). It runs before any checkout of the standard exists, so it carries its own copy of the rule, and a test holds that copy to `shell_quoted`. **How tested.** `tests/test_family_printed_quoting.py`, 69 tests: - The quoter round-trips through `shlex.split`, and through a PowerShell reader, for a space and an apostrophe, `é` and a typographic apostrophe, shell syntax, the empty string, and every printable ASCII character alone and before, inside and after a word. - The host's own `sh`, `bash` and `zsh` hand each quoted word back byte for byte through `printf`, a leading `=` among them (skipped where a shell is absent, and on Windows). - It agrees with the doctor's `quote_arg` on every one of those ASCII values but a leading `=`, and its two alphabets are the doctor's two patterns. - Under a holder in `my work` and in `Dana's work`, each of these splits into the argv it means: `init`'s NEXT (with `cd` and `make siblings` on their own lines), `add`'s NEXT, the cached-store remedy and note, the `mv` lines, and setup-project's `switch` and `add`. - The `rm -rf`, run as split, deletes the store, and the decoy `<tmp>/my` survives. - The `mv` lines, run as split, move the holder. - The holder's own `siblings.py` runs `--help`, the misnamed-folder warning and `--make park` beside a `repo_shape.py` that lacks every name this change added. - Three existing tests' expected lines follow the new wording. Mutations, each turning tests red: - a bare quoter: 35 red; - bare `mv` lines in `siblings.py`: 3 red; - a bare `rm -rf`, a bare `add` NEXT, a bare `init` `add` (round 1, on the 85-test file): 2 red each; - `siblings.py` importing `shell_line` again: 1 red (the older-`repo_shape.py` test); - `%` dropped from the POSIX alphabet: 2 red (the every-ASCII agreement and the alphabet test). The round-1 sample test passed it, 36 of 36; - a leading `=` left bare: 10 red, among them the real-`zsh` test; - `cd … && make siblings` back on one line: 2 red; - setup-project's holder unquoted: 2 red. **What was wrong.** - AGENTS.md says each act's ONE commit lands as a pull request, and no step made the branch that pull request needs. - After `init` the holder stands on `main`, and all three acts committed straight onto it. - `add` printed `NEXT git -C <holder> push -u origin <branch> && open a pull request`. No branch is called `<branch>`, so the push failed: `src refspec … does not match any`. - `bump` and `remove` printed no NEXT at all. - `bump-leg.py` refuses exactly this for a leg (`bump-leg-on-tracking-branch`). **What changed** (commit 3, and the review commit after it). - The real run of each act refuses the holder's tracking branch, the one `family.yaml` declares (`family-on-tracking-branch`), and a HEAD on no branch (`family-holder-detached`, which is what `bump-leg.py`'s `bump-leg-root-detached` refuses too). - The check comes after the refusals about the arguments and the holder, and before the member is read, fetched or cloned, and before anything is written. - The remedy is a quoted `git -C <holder> switch -c <branch>`. The branch is `family/add-<project>`, `family/bump-<id>-<sha12>` or `family/remove-<id>`, and a name that is not a plain branch word becomes `member`. - `--dry-run` runs on any branch. On a work branch it prints where the commit would land. On the tracking branch it prints that the real run refuses there, with the refusal's code, and the `switch` command on a line of its own. The module's EXIT CODES paragraph names this as the one refusal a dry run does not make. - Each act that commits ends with ONE pasteable line: `NEXT git -C <holder> push -u origin <the branch it committed on>`, quoted, then `then open a pull request against <tracking>; never push to it directly`. `bump` and `remove` gain that NEXT. - `init`'s NEXT STEPS open with the `switch` before the `add`. - `add`, `bump` and `remove` `--help` say the branch rule, and that each ends by PRINTING the push; docs/cli.md is regenerated. - AGENTS.md's family block gains the `switch` step before the `add`, and step 3 says the tracking branch is refused, as `family-on-tracking-branch`, and that the branch is pushed as its NEXT line says. README's InkRouter block gains the same step, and its paragraph says it. Both edits are line-neutral: AGENTS.md tightens steps 2 and 5, and README tightens the `make bootstrap` paragraph. `docs/handbook.html` mirrors the block and the sentence, and its footer names #487 and the new README blob. - The holder's own docs, `templates/family-root/AGENTS-shape.md` and `templates/family-root/README.md`, open their add/bump/remove block with the `switch` and say the act refuses the tracking branch. `setup-project.py`'s family hand-over prints the `switch` before its `add`. - Test fixtures that committed on `main` now commit on a branch: - `tests/test_family.py`'s module fixture grows the family on `family/members` and pushes that branch to the remote's `main`, as the merged pull request would leave it; - `test_shape_doctor.py`'s holder fixture and `test_family_add_excludes.py`'s `empty_holder` switch to a branch; - `test_family_add_refusals.py` changes in four places: the `NEXT` expectation, `unborn_holder`, the empty-family test, and the corrupt-HEAD test. In that last test, the tag state's branch check is answered in-process, because the test is about the rollback's reading of HEAD. **How tested.** `tests/test_family_tracking_branch.py`, 17 tests: - For each act, on `main`: refused, with the holder byte for byte as it was, and for `add` nothing cloned. Then the printed `switch`, run as split, makes the branch; the act goes through on it; and its printed NEXT, run as split, pushes the branch, which the remote then holds at the act's commit. - A detached HEAD is refused. - Dry runs on `main` and on a branch say what they should. - A family made with `--tracking-branch trunk` refuses `add` on `trunk`; on a branch named `main` the dry run says the commit lands there for a pull request against `trunk`, the real run commits, and its NEXT pushes `main`. - The suggested names pass `git check-ref-format --branch`. - `init`'s NEXT STEPS open with the `switch`. - AGENTS.md, README.md and the holder's two docs show the `switch` in the same block as, and before, the first `add`. Mutations, each turning tests red: the branch check letting `main` through, 4 red; the NEXT line naming `<branch>` again, 3 red; `init`'s NEXT STEPS without the `switch`, 1 red; the tracking branch hard-coded to `"main"`, 1 red (the `trunk` test; every family test passed it before); the holder docs without the `switch`, 2 red; setup-project without the `switch`, 2 red. An independent review at 10c93de gave CHANGES NEEDED (0 blockers, 1 should-fix, 6 nits). The should-fix was a holder that kept an older `scripts/repo_shape.py` through `apply --accept-local`: its new `siblings.py` could not import the shared quoter, so `make siblings`, `park` and `resume` died. The round at bb11b63 fixes it and all six nits, as described above. Re-verified at bb11b63: APPROVE WITH NITS (0 blockers, 0 should-fixes, 3 nits), with SonarCloud's quality gate OK and 0.0% new duplication. The nits were the holder README's pointer to the push (now "as its `NEXT` line says"), two Sonar smells (S1192: `refs/heads/` is now `HEADS_PREFIX`; S3415: the two expected-first assertions now put the actual value first, as the file's other 32 do), and this description's list of other pull requests. All three are fixed at 3451a56: commit 11ae0a8, then a clean merge of main 994be39. The family set gave 566 passed, 8 skipped there. GitHub's update-branch then merged main cac6bb2 as 8e1471c, and main aa4e1f5 (#508, after #490, #474 and #468) was merged as 708f3bb. There only the `docs/handbook.html` footer conflicted: it takes main's clauses, #468's (#424) included, with the README blob recomputed, and `docs/cli.md` auto-merged and passes `--check`. Main 47b1e06 (#505 and #395, after #376, #374, #384, #495, #493 and #491) was then merged as fcbc7a2, again with only the footer in conflict: main's clauses stay in main's order, this branch's (#487) moves to the end, after (#483), and the README blob is recomputed. Every run used `/usr/bin/python3 -m pytest -q -p no:cacheprovider`, with a stub `gh` first on PATH, a bogus `GH_TOKEN`, an empty `GH_CONFIG_DIR` and git's identity blanked. Run on Linux. At e42ef74 (main d41cc29, #402, merged in by GitHub's update-branch), on its tree 655c927: the first set listed below for fcbc7a2, plus every `tests/test_update_shape*.py`, gave **1242 passed, 22 skipped**; the second set, plus `tests/test_leg_path_yaml_scalars.py`, `tests/test_ruleset_hint_tracking_branch.py`, `tests/test_scaffold_reuse_seeded_legs.py`, `tests/test_scaffold_pin_and_reuse.py` and `tests/test_doctor_validator_rows.py`, gave **1260 passed, 12 skipped**. The skips are the same as at 5a2776d, below. At 654df49 (main 8cd7dbe merged in, with #506 and #347): the first set listed below for fcbc7a2, plus every `tests/test_update_shape*.py`, gave **1242 passed, 22 skipped**; the second set, plus `tests/test_leg_path_yaml_scalars.py`, `tests/test_ruleset_hint_tracking_branch.py`, `tests/test_scaffold_reuse_seeded_legs.py` and `tests/test_scaffold_pin_and_reuse.py`, gave **1231 passed, 12 skipped**. The skips are the same as at 5a2776d, below. At 5a2776d (main f391e58 merged in, with #266, #509, #496 and #435): the first set listed below for fcbc7a2, plus every `tests/test_update_shape*.py`, gave **1213 passed, 22 skipped** (the template-quoting skips below, and three NTFS-junction cases, Windows only); the second set, plus `tests/test_leg_path_yaml_scalars.py`, gave **1171 passed, 12 skipped** (the four Windows-only skips below, and eight in that file, main's new one, whose cases the quoted templates (#259) leave with nothing to refuse). At 22733ce (main 631f087, #503, merged in by GitHub's update-branch): the first set listed below for fcbc7a2, plus every `tests/test_update_shape*.py`, gave **1213 passed, 22 skipped** (the template-quoting skips below, and three NTFS-junction cases, Windows only); the second set gave **944 passed, 4 skipped**. At fcbc7a2 (origin/main 47b1e06 merged in, with #505 and #395): - every `tests/test_family*.py`, `tests/test_validate_family*.py`, `tests/test_park_resume_targets.py`, `tests/test_free_plan_hint.py` and `tests/test_template_text_placeholders.py`: **844 passed, 19 skipped** (`tests/test_family_init_values.py:219` and `:291`, which skip because the template now quotes the placeholder and `id:`). - `tests/test_repo_hygiene.py tests/test_agents_md_codes.py tests/test_cli_reference.py tests/test_shape_doctor.py tests/test_setup_project_py.py tests/test_setup_sh.py tests/test_template_quoted_values.py tests/test_stdout_encoding.py tests/test_windows_paths.py tests/test_commit_trailers.py`: **944 passed, 4 skipped** (the doctor's PowerShell line and `tests/test_stdout_encoding.py:286`, Windows only). At 708f3bb (origin/main aa4e1f5 merged in): - every `tests/test_family*.py`, `tests/test_validate_family*.py`, `tests/test_park_resume_targets.py` and `tests/test_free_plan_hint.py`: **718 passed, 8 skipped** (`tests/test_family_init_values.py:242`). - `tests/test_repo_hygiene.py tests/test_agents_md_codes.py tests/test_cli_reference.py tests/test_shape_doctor.py tests/test_update_shape_landing.py tests/test_doctor_unfinished_merge.py tests/test_template_values_single_pass.py tests/test_stdout_encoding.py tests/test_windows_paths.py`: **813 passed, 4 skipped** (the doctor's PowerShell line and `tests/test_stdout_encoding.py:286`, Windows only). Earlier, with origin/main 48585e6 merged in: - `tests/test_family*.py tests/test_validate_family*.py tests/test_stdout_encoding.py tests/test_windows_paths.py tests/test_repo_hygiene.py tests/test_agents_md_codes.py tests/test_cli_reference.py tests/test_park_resume_targets.py tests/test_bootstrap_spelling.py tests/test_free_plan_hint.py tests/test_template_values_single_pass.py`: **1303 passed, 9 skipped**. - `tests/test_shape_doctor.py tests/test_update_shape*.py tests/test_commit_trailers.py tests/test_tracking_branch_ref.py tests/test_scaffold_ignored_shape_files.py tests/test_ruleset_hint_tracking_branch.py tests/test_bump_leg_dry_run_tracking.py tests/test_setup_project_py.py`: **999 passed, 6 skipped**. - The new files alone: `test_family_init_landing.py` 9, `test_family_printed_quoting.py` 69, `test_family_tracking_branch.py` 17, all passed. The full suite was not run locally. CI's three jobs are the check. #505 and #395 have since landed, and both are merged here at fcbc7a2. Each of #505's family sentences was read against this branch's code, and none is false: AGENTS.md 3 and 233; README 569, 822, 1152, 1269 and 1348; the handbook 1126 and 1506. #395 landed with its test switching to `family/members` before its real `add`, as the last bullet below says, and the first run above includes it. #503 then landed as 631f087, and GitHub's update-branch merged it here as 22733ce with no conflict. Main f391e58 (#266, #509, #496 and #435) was then merged by hand as 5a2776d, again with only the footer in conflict: main's clauses stay in main's order, this branch's (#487) moves after #509's (#498), and the README blob is recomputed; `git show --remerge-diff` shows nothing else. #509's reworded sentences in AGENTS.md, README.md and the handbook were read beside this branch's, and none contradicts or repeats another. Its `setup-project.py` changes (the clone-path check, the plan line, the step-(7) backstop and `_main`) and this branch's `shell_word` lines in the hand-over do not meet, and `docs/cli.md` passes `--check`. Main 8cd7dbe (#506 and #347) was then merged by hand as 654df49, again with only the footer in conflict: this branch's (#487) moves after #506's (#462), the README blob is recomputed, and `docs/cli.md`, which both sides changed, still passes `--check`. #506's two `scripts/family.py` hunks (the `FAMILY_RULESET_HINT` import, and its print when the holder's push is refused) sit unchanged inside this branch's code, and that hint prints no path. Under `--reuse-empty-repo`, `init` still refuses its default `--into` inside a checkout of this standard (`family-landing-in-standard`) before it reads the remote, as a dry run with a stub `gh` showed. #506's sentences in AGENTS.md, README.md and the handbook are still true beside this branch's. Main d41cc29 (#402, for #277), which rewrote the doctor's NEXT lines and `tests/test_shape_doctor.py`, was then merged by GitHub's update-branch as e42ef74, with no conflict; the `holder` fixture still switches to `family/members` before its `add`. AGENTS.md stays at 490 lines and README.md at 1422. `git merge-tree --write-tree` of 708f3bb, at 04:47Z on 2026-10-13: - **Clean:** #503 (`fix/family-makefile-without-siblings`) and #395. Each merged tree keeps AGENTS.md at 490 lines and README.md at 1422. - **`docs/handbook.html` footer only:** #505 (`docs/v1-accuracy`) and #491. #468 has landed and is merged here. #506 conflicts there too, and in `scaffold-project.py`, as it does with main. README.md and AGENTS.md merge clean with #505, and `setup-project.py` merges clean with #491. Whichever lands second keeps every footer clause and recomputes the README blob. - **#395 merges clean but one of its tests fails with this branch.** `tests/test_template_text_placeholders.py::test_a_member_whose_id_yaml_would_misread_is_pinned_under_that_id` inits its own holder and runs a real `add` on `main`, which #487 refuses with `family-on-tracking-branch`. Whichever of the two lands second adds `git("switch", "-q", "-c", "family/members", cwd=root)` after that test's `init`; lane 5 is adding it on #395's own branch now, so this branch needs no change for it. (#395's head d526f76 also conflicts with this head in `tests/test_adopt_manifest_values.py`, a file this branch does not touch: that is #395 against main, which the same round resolves.) - On Windows `shell_quoted` speaks PowerShell, the native shell this standard documents (`setup-project.py`), as the doctor's NEXT lines do. `git -C …`, `python scripts\family.py …` and `cd …` are shell-agnostic, so they get the platform's quoting, and no printed line joins two commands with `&&`, which Windows PowerShell 5.1 rejects. - `rm -rf`, `mv` and `mkdir -p` are POSIX commands. They are quoted for a POSIX shell on every host, and the text says "in a POSIX shell (Git Bash on Windows)". PowerShell refuses `rm -rf`'s `-rf` and deletes nothing. - The tests split a host-quoted line with a PowerShell reader on Windows and with `shlex` elsewhere, and split a POSIX-only line with `shlex` everywhere. The real-shell test is skipped on Windows. - On Windows the tests do not execute `rm` or the `mv` lines. They delete the split path with `shutil.rmtree`, and stop after checking the `mv` argv. - Paths: the cached store keeps git's POSIX spelling (`as_posix`), as before. Test paths are `resolve()`d, which expands an 8.3 short name such as `RUNNER~1`. - Nothing here was run on Windows or macOS locally. The `tests-windows` and `tests-macos` CI jobs are the check; on macOS the real-shell test runs the shipped `zsh`. - **A shipped template that imports a newer shared helper can break a holder that kept an older copy.** This change no longer does (`siblings.py` uses `shlex.join`), but nothing stops the next one: `update-shape.py apply --accept-local scripts/repo_shape.py` keeps a `repo_shape.py` that lacks a name a newly copied file imports, and `check` and the validator then read the holder as fine. The general guard is for `apply` to refuse that, as rv456 found for `bootstrap.py` and `spelled` (#456). - **The doctor's `quote_arg` leaves a word that begins with `=` bare**, which zsh expands. `shell_quoted` quotes it; the doctor is left as it is here, and the agreement test excludes that one case. - **The doctor and `update-shape.py` keep their own quoters.** The doctor's `quote_arg` flattens to ASCII by its report's contract, so moving it onto `repo_shape.shell_quoted` is more than an import change. `update-shape.py`'s POSIX `shlex` lines are lane 3's open #374. A test holds `shell_quoted` and `quote_arg` to one answer on every printable ASCII value but a leading `=`. - **`family.py`'s by-hand rollback commands (#365) and its `git config -f .gitmodules` removals (#317, #360) keep `shlex.quote`.** They were already quoted, they are POSIX-only (`printf … | git update-index`), and they are worded for a POSIX shell. - **The holder's `validate-family.py` remedies that name `family.py add`** (`member-gitlink-absent`, `member-gitlink-unnamed`) print no branch step: they are a holder copy, and the `add` they name refuses the tracking branch with its own `switch` remedy. - **`remove`'s store note still computes `.git/modules/<path>`** rather than asking `git rev-parse --git-path`, as `add` does. In a linked worktree it therefore prints no note. That predates this change. - **PowerShell verbs** (`Remove-Item`, `Move-Item`) are not printed for the POSIX-only commands. - **README's `--into` sentence gives the default but not the refusal.** README.md (861) and the handbook (1246) say `--into <dir>` is the PARENT directory, "the directory you are standing in, by default". They do not say that `init` refuses that default inside a checkout of this standard (`family-landing-in-standard`). AGENTS.md item 1 says so, and the README's own example passes `--into ..`. README.md is at its 1422-line cap, so the sentence is on #459's checklist. - **`setup-project.py`'s own `cd <clone>` line and its step-(7) backstop lines print their paths unquoted.** This change quotes only the family lines it prints. Those two predate it and #509, and they are on #459's checklist. Reviewed independently at 10c93de: CHANGES NEEDED (0 blockers, 1 should-fix, 6 nits). The round at bb11b63 fixed the should-fix and all six nits, and its re-verification gave APPROVE WITH NITS (0/0/3), with SonarCloud's quality gate OK; those three are fixed at 3451a56. Main was then merged by GitHub's update-branch (8e1471c), by hand at 708f3bb and at fcbc7a2, where each time only the handbook footer conflicted, and by update-branch again at 22733ce (main 631f087, #503), with no conflict. CI was green there on Linux, macOS and Windows, with SonarCloud passing, but main had moved on: main f391e58 (#266, #509, #496 and #435) was merged by hand at 5a2776d, where again only the footer conflicted. CI was green there too, but main moved on again: main 8cd7dbe (#506 and #347) was merged by hand at 654df49, where once more only the footer conflicted. CI was green there as well, and main d41cc29 (#402) then came in by update-branch at e42ef74, with no conflict. CI is green on Linux and macOS, with SonarCloud passing, at e42ef74, a clean merge of main. All three OSes and SonarCloud passed at 654df49, and under Brett Heap's landing rule of 22:08:06Z (ruling 27) macOS and Windows carry over from that head. Main e6fbd74 (#515, for #514), which landed after it, touches none of its files, and the combined tree passed `tests/test_adopt_pins.py`, `tests/test_shape_doctor.py`, `tests/test_scaffold_leg_path_collisions.py`, the hygiene, AGENTS-code and CLI-reference tests, `tests/test_family_printed_quoting.py` and `tests/test_family_tracking_branch.py` (777 passed, 3 skipped). Closes #488 Closes #486 Closes #487 Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap
added a commit
that referenced
this pull request
Oct 13, 2026
…ndard it ran from and names it on the report's last line, so a NEXT line pasted after the command has exited runs instead of dying with `can't open file`, and it hands `--json` and `--placement-plan <file>` on to `shape-doctor.py` instead of refusing them (#500) (#510) Installed, `openRepoShape --doctor` has no checkout beside it, so it clones the standard into `mktemp -d` and runs `shape-doctor.py` from there. The doctor builds its NEXT lines from that clone: `update-shape.py check --upstream <clone>`, `adopt-project.py plan`, `setup-project.py --preflight`, the template validators. An EXIT trap then removed the clone as the command returned, so each of those lines, pasted, died with `python3: can't open file`, exit 2. And `--doctor` refused every argument but the path, so `--json` (AGENTS.md doctor step 7) and `--placement-plan <file>` (the `placement` row's own NEXT, step 4) could not be asked for at all. Brett Heap's ruling, 2026-10-13: "Keep the run's clone". - The EXIT trap is disarmed once the doctor has RUN, and not before. A refusal on the way (the clone failed, the ref did not check out, the ref carries no `shape-doctor.py`) printed no NEXT line and still leaves nothing behind. So does an interrupted run, because bash runs the EXIT trap on INT, TERM and HUP. Beside a checkout nothing is cloned, and nothing is kept or named. - The clone lives in `${TMPDIR:-/tmp}/openRepoShape-doctor.XXXXXXXX`, so a person can find every directory they have been left, and it is resolved with `pwd -P`, because the doctor prints its standard resolved (macOS's `$TMPDIR` sits under `/var` -> `/private/var`). Every step of making it is checked: `--doctor` runs under `set +e`, and an unchecked `cd -- ""` after a failed `mktemp` would have pointed the trap at the person's own directory. - The report's last line names the directory, says that any NEXT command above runs from it, and says it is safe to delete once the person is done with them. The doctor can refuse (a missing `<path>`, a `--placement-plan` it cannot write: exit 3) with no report and no NEXT line, and the clone is kept all the same, so the line never claims there are NEXT commands. Under `--json` that line goes to stderr. stdout is a JSON object a caller parses whole, one more line makes it a document `json.load` refuses, and the object already names the clone as `standard`. Without `--json` it stays on stdout, so a report saved to a file still says what it left behind. The line hands out no `rm` command: an agent told to "run the command the row names" would delete the clone before the commands it holds had run. - `--json` and `--placement-plan <file>` (or `--placement-plan=<file>`) are the doctor's when they come AFTER `--doctor`, the way a subcommand's flags follow it. Anywhere else they are the lone flags they always were, passed on to setup.sh, which refuses them, so a scaffold run reads those words as before. The file reaches `shape-doctor.py` as `--placement-plan=<file>`, so a name beginning with `-` is still a file. A missing file, an empty one, or a flag where the file goes is refused by name, exit 2. - The doctor runs in the person's own directory and is never `cd`'d, so a relative `<file>` lands where the command was typed: not in the repository, and not in the clone. - A path may follow the flags (`--doctor --json ../Atlas`). A word before `--doctor`, or a second path, is still a `<Project>` and is refused as before. The refusal's wording changes: it named "a path and nothing else", and now gives the full form. - `openRepoShape --help`'s `--doctor` paragraph is the text this changes. It now names the two flags and the kept clone. Its verdict list gains `MISPLACED` and `CANNOT ANSWER`, and `COMPLIANT, SHAPE BEHIND` gets its comma back. Its exit codes now say that 3 means the doctor cannot answer and that this command's own refusals exit 2. The old text said "3 usage", which no refusal here exits. `docs/cli.md` is regenerated. Tests (tests/test_shape_doctor.py, shim section; offline, local bare standard, the refusing `gh`/`curl` stubs, `$TMPDIR` the test's own): - the stdin test now asserts the opposite of what it pinned: the clone outlives the run, is the only entry in `$TMPDIR`, is named on the last line, and carries the `openRepoShape-doctor.` prefix; - the installed file, alone in a directory, over a drifted project: every NEXT that names the kept clone runs after the command has exited, as `pasted()` splits it, and the `shape currency` one reproduces `locally-modified scripts/bootstrap.py`; - `--json` from the installed form parses as one object, the kept line is on stderr, and the rows equal the beside-a-checkout `--json` rows with only the standard's path differing; - `--placement-plan` (relative, path-then-flag and `=`-flag-then-path): the plan lands in the directory the command ran from and nowhere else, and the project's tree is unchanged; - six refusals (a scaffold flag, a second path, `--json` before `--doctor`, a missing, empty or flag-shaped plan file), all exit 2, nothing on stdout, nothing cloned, nothing fetched; - tests/test_openreposhape_command.py: a ref with no `shape-doctor.py` is refused and leaves `$TMPDIR` empty. Mutants: with main's `openRepoShape` put back, 12 of these 15 fail. With only `trap - EXIT` removed, the stdin, NEXT-lines and `--json` tests fail. Docs (Brett Heap, 2026-10-13T11:30Z: each lane-4 fix carries its own doc lines). These are reworded in place, at the line caps: - AGENTS.md: the compliance section's installed-form sentence (#505 said the clone is "deleted on exit" and the form "takes a path and nothing else"), and step 7 (`--json` is one object, alone on stdout); - README.md: the doctor paragraph ("writes nothing into the repository and fetches nothing from it"; installed, it KEEPS the clone and its last line names it), `--json` "on stdout", and `--placement-plan` as all `shape-doctor.py` ever writes; - docs/handbook.html mirrors all three and re-hashes `README.md blob`. Review round 1, the independent review at 9808aec (all three OSes and Sonar were green there), and the merges of main (#509, #506 and #507 each rewrote the same handbook footer lines): - SHOULD-1: the kept line said "because NEXT commands name files in it" even when the doctor refused and printed none. It now reads "the standard this run used is kept at <dir>, and any NEXT command above runs from it; it is safe to delete once you are done with them." When the clone is kept is unchanged. A test pins the refusal case: a missing `<path>`, exit 3, one line on stdout. - NIT-1: the `--help` synopsis line reads `openRepoShape --doctor [<path>] [--json] [--placement-plan <file>]`, the full form the paragraph below it and the refusal already give. `docs/cli.md` is re-rendered. - NIT-5: `--help` said "It writes nothing into the repository" and then exempted a file that can be in the repository. It now says the doctor writes no file but the `--placement-plan <file>` you name, at the path you give, and that a relative one lands in the directory you ran this from, even inside the repository. - docs/handbook.html: the footer keeps #509's "added (#498)" clause, #506's "added (#462)" clause and #507's "added (#487)" clause and adds this one after them, with `README.md blob` re-hashed from the merged README.md. Closes #500 Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
brettheap
pushed a commit
that referenced
this pull request
Oct 14, 2026
Merged by sha, e6fbd74, fetched at 22:23Z, on top of 180902f. It brings seven commits since 191105a, oldest first: cdcd2f0 (#509), fc48854 (#496), f391e58 (#435, the plan's field types), d52b100 (#506), 8cd7dbe (#347), d41cc29 (#402) and e6fbd74 (#515 for #514: `execute` writes each `neutral_product_pins:` entry so it reads back as the text `plan` recorded). No conflict: the tree is `git merge-tree`'s (8178281). One function changed on both sides, `_template_table`: this branch spells three plan keys by their constants (`ELECTED_ON_KEY`, `REFERENCE_KEY`, `ELECTED_BY_KEY`), and #514 takes the pins' list from `_neutral_product_pins(pins)`. Both are kept. No top-level name is bound twice in adopt-project.py; shape_materialize.py and family.py import `parse_yaml` and `YamlError` twice on both parents, the same objects. #435 comes first. `check` returns on `_plan_type_findings` before any of this branch's questions, and `execute` calls `_refuse_a_mistyped_plan` before it opens the source. Probed: `source:` or `legs:` set to 7, true or a list, and `allow_empty_legs:`, `paths:` or `pins:` set to something that is not a list (16 plans). Each is one finding at `check`, and `execute` refuses it before the work directory or a remote exists. None prints `manifest-misread` or #181's remediation, and none ends in a traceback. #514 quotes the one line this gate still found a misread on. This gate asks `_template_table` (`_pin_field`), so it asks the quoted line with no edit here. At this merge the quoting file, main's tests/test_adopt_manifest_values.py and tests/test_adopt_pins.py and tests/test_template_text_placeholders.py give 14 failed and 803 passed. They are the 14 rows round K4 named, each asking what #514 changes. The next commit makes them true. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Lane: openreposhape-4 (openRepoShape-4)
What was wrong (#498, audit finding auS1-02)
README's "Rehearse first, creating nothing" runs the scaffold against
--local-remote-dir ./rehearsaland leaves./Atlasbehind, "both of which you can delete". Someone who kept it and then ran the real command in the same folder went through the preflight, the naming check, the plan and the yes. Step (6) then created and pushed all three repositories. Only then did step (7) find./Atlasand refuse. Its printedgit clonefailed on the same directory, and a re-run was refusedscaffold-remote-exists.resolve_familydecides the clone path in step (3), but nothing read that path before step (7).What changes (setup-project.py; setup.sh hands over to it unchanged)
resolve_family:refuse_a_taken_clone_pathrefuses, with exit 2, when anything stands at<into>/<Project>.os.path.lexists, so a symbolic link to nothing counts.git clonerefuses one too.originon this disk, named<Project>.git, and aproject.yamlat the root) is said to look like one. Its way out is "Move it, or delete it if nothing in it is yours, then run this again." A working clone of a local or network repository looks the same, so the tool never asserts it (review round 1).user:token@dropped;.gitwhoseorigincannot be read;originis read withgit config --file <clone>/.git/config, never withgit -Cand never with git run inside the clone.git -Cwould walk up from an empty.gitand report theoriginof the repository that holds<into>. Running git inside the clone would read a config that can name a command.clone into <path>after the dry run, on every run. That path is the one thing the run writes on this disk, and the plan never named it before the yes.lexists. Its way out now says to move the thing aside first and to run the hand clone from the clone's parent.Example
Tests
New tests. The runs through both entry points are written once, as two helpers in
tests/conftest.py. Each test file calls them through a one-call wrapper, so Sonar's duplication gate holds.assert_a_rehearsals_clone_stops_the_real_runfollows README's order in one folder: a rehearsal, then a real run into a second--local-remote-dir. The real run must refuse in step (3), say the clone looks like the rehearsal's, offer deleting it only if nothing in it is the person's, and leave the second remote directory absent. The rehearsal's plan must nameclone into <path>between the dry run and step (6).assert_a_taken_clone_path_is_refusedruns three cases, all with--yes: an empty directory, a file, and a directory that is not a clone. Each must be refused in step (3) with the exact wording, create no remotes, and leave the path untouched.tests/test_setup_project_py.py:what_stands_atcases, among them a token dropped from a GitHuboriginand an empty.gitinside another repository, which must not be reported as that repository's clone;plan_and_confirmcalls pass the newcloneargument. The family no-terminal rehearsal also asserts theclone intoline.Targeted run: 386 passed, 0 failed, 0 skipped, in 3 min 47 s. Files:
tests/test_setup_project_py.py,test_setup_sh.py,test_openreposhape_command.py,test_windows_paths.py,test_cli_reference.py,test_agents_md_codes.pyandtest_repo_hygiene.py.Mutants. Each mutant was run in a scratch worktree with this branch's test files:
setup-project.pyatcc8f2da4): all 19 selected tests fail: the 18 new ones and the amended family rehearsal. The base creates all three remotes and refuses in step (7) with the old "Finish by hand" text, prints noclone intoline, and lacks the new functions.lexistschanged toexistsin step (3): the link test fails,DID NOT RAISE Refusal.Review round 1 (independent review at
427237a: NOT MERGEABLE on one MUST)All folded into this push, with the merge of
origin/mainand the docs commit. The review fixes are their own commit,7a97d53b:file://repository named<Project>.gitthat holds aproject.yamlwas told "It is the clone a rehearsal left behind … Move or delete it", even with unpushed commits.git config --fileis kept, and no git runs inside the clone.origincarryingESC[2Jand a newline before a forgedREFUSED:is written into.git/configas bytes. The test asserts that git reads it back as written, and that it is never echoed. This pinsCONTROL_RE.lexists. It is skipped whereos.symlinkis refused.without_credentialscuts to the LAST@before the path, and now applies to the rehearsal sentence too. Tests:https://dana:p@SECRET3@github.com/..., andfile://dana:SECRET7@/....originreads "It is a directory holding a.git." instead of "It is a git repository."protocol.file.allowunder--local-remote-dir, PowerShell&&, unquoted paths) are on main already.Mutants for the review, run in a scratch worktree at
121c204bwith the fixed tests (loglogs/mutants-review-round-1.log). Each one is killed:CONTROL_REfilter dropped (the reviewer's M5)lexistschanged toexists(M6)@regexTargeted run at
7a97d53b: 392 passed, 0 failed, 0 skipped, in 3 min 22 s (loglogs/targeted-2.log). The files are the round-0 set:test_setup_project_py,test_setup_sh,test_openreposhape_command,test_windows_paths,test_cli_reference,test_agents_md_codesandtest_repo_hygiene. The docs commit had already passedtest_repo_hygiene,test_cli_reference,test_agents_md_codesand the two setup files: 295 passed, inlogs/docs-run-1.log.Docs
The docs are corrected in this PR. Brett Heap ruled at 2026-10-13T11:30Z that each lane-4 fix PR carries its own doc lines. The branch first merges
origin/mainat47b1e06c, which brings in #505 (af01068b), with no conflicts. The doc commit then corrects these lines; each file keeps its line count (README.md 1422, its cap; AGENTS.md 490, its cap; docs/handbook.html 1933):./Atlasand the rest "all deletable; a real run here refuses up front while./Atlasis there", where it used to say "all of which you can delete". The rest of the paragraph is rewrapped with its words unchanged.<into>/Atlas.<into>/<Project>, a rehearsal's clone included. The assistant asks the human to move or delete it and never does either itself.scaffold-leg-path-collidesandscaffold-pin-collidesare still spelled.openRepoShape <Project>) checks the clone path<into>/<Project>only after all three repositories exist, so a person who follows README's "Rehearse first" and then runs for real in the same folder, where the rehearsal left./Atlas, gets three new GitHub repositories, a refusal, a printed hand clone that fails on the same./Atlas, and a re-run that is refusedscaffold-remote-exists#498)" and the new README blob.No
--helptext changed, sodocs/cli.mdstands.Shared files
initrefuses to land in the standard's checkout and names a runnablescripts/family.py, printed commands quote paths, andadd,bumpandremoverefuse the tracking branch and print their branch's push (#488, #486, #487) #507 (fix/family-commands-and-branch) addsshell_wordand changesfamily_next_commandin section 8 ofsetup-project.py. The lines differ, andsetup-project.pyauto-merges. Itsdocs/handbook.htmlconflict with main is its own.project.yaml, the leg pins andfamily.yamlquote their text placeholders, so--id 007,--tracking-branch 2026,--spec-path trueor--org nullread back as typed, andfamily.py addquotes a member id alike (#259) #395 (fix/template-quoted-placeholders) changes onevisibilityassertion in each oftests/test_setup_project_py.pyandtests/test_setup_sh.py. The lines differ, andgit merge-treeis clean.initrefuses to land in the standard's checkout and names a runnablescripts/family.py, printed commands quote paths, andadd,bumpandremoverefuse the tracking branch and print their branch's push (#488, #486, #487) #507 lands, itsshell_wordcould quote the paths in the step (7) backstop's printed commands. It is not onmainyet, and this change does not depend on it.Closes #498
🤖 Generated with Claude Code