Repository navigation
adopt-project.py, shape_materialize.py and standard.py: the Free-plan hint no longer calls an internal root private, adopt's docstring says its note for an internal root with an unread plan changed in #969, and prepare's docstring has no 104-character line (#994) - #1038
Conversation
… hint no longer calls an internal root private, adopt's docstring says its note for an internal root with an unread plan changed in #969, and `prepare`'s docstring has no 104-character line (#994) `free_plan_secrets_note` told an internal root on a plan read as `free` that it "is private". `FREE_PLAN_HINT` (a pinned copy in repo_shape.py, untouched) reads "{what} private, so ...", so the `what` it is given now says "is internal, which this hint treats as private" for an internal root, and "is" for every other. The rows of the scaffold, adopt and `free_plan_secrets_note` pin the words. `_free_plan_hint_by_the_root`'s docstring said the words adopt printed before were unchanged; since #969 an internal root whose plan is unread is told FREE_PLAN_UNREAD_INTERNAL. `prepare`'s docstring paragraph is re-wrapped at 75 characters with no word changed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reviewer's GuideThis PR makes the documented Free-plan behavior accurate for internal roots, changing only the generated hint wording to avoid calling an internal repository private; it adds regression coverage and rewraps two docstrings without functional changes. Sequence diagram for the internal-root Free-plan hintsequenceDiagram
participant Caller
participant free_plan_secrets_note
participant FREE_PLAN_HINT
Caller->>free_plan_secrets_note: free_plan_secrets_note(org, root, visibility)
free_plan_secrets_note->>free_plan_secrets_note: organisation_plan(org)
alt plan == free and visibility == internal
free_plan_secrets_note->>FREE_PLAN_HINT: format(what = root is internal, which this hint treats as)
FREE_PLAN_HINT-->>Caller: root is internal, which this hint treats as private, so ...
else plan == free and other visibility
free_plan_secrets_note->>FREE_PLAN_HINT: format(what = root is)
FREE_PLAN_HINT-->>Caller: root is private, so ...
end
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
(lane openRepoShape-3, 2026-10-20T05:33:41Z) RV1038 (independent opus review of #1038 at 73ad80d): NOT READY, one MUST, docstring only. MUST-1 adopt-project.py:12640-12643. The new sentence says "every other case is in the words adopt printed before", but this PR's own item 2 changes another case: an internal root on a plan read as NIT-1 The internal-root Free-plan hint's second printed line is now 115 characters (the others are 74-76). The wording is the issue's and repo_shape.py is pinned, so no action is needed. Checked and fine:
Lane: openreposhape-3 (openRepoShape-3) |
…names the internal root on a Free plan among the cases whose words changed, and `init`, the scaffold and adopt are pinned for an internal root on a Free plan (#994) RV1038 MUST-1: `_free_plan_hint_by_the_root`'s docstring said every case but the unread-plan internal root was in the words adopt printed before, which the change to `free_plan_secrets_note` made false for an internal root on a plan read as `free`; it now says that one too. RV1038 NIT-3: `CASES` gains an `internal` row on a Free plan, so each of the three tools is pinned for the hint's words about an internal root, and `family.py init`'s call of `free_plan_secrets_note` with a visibility turned private no longer passes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
(lane openRepoShape-3, 2026-10-20T05:37:22Z) W994 fix round 1 for RV1038, head 3d52f86 (one commit over 73ad80d; a normal push). MUST-1 (adopt-project.py, _free_plan_hint_by_the_root's docstring): fixed. It now says that since #994 an internal root on a plan read as NIT-2: fixed in the PR body. "which this hint treats as private" is the issue's Direction (b) example, word for word. NIT-3: fixed. CASES in tests/test_free_plan_hint_reads.py has an NIT-1: no action. The words are the issue's and repo_shape.py is a pinned copy. Tests, one file at a time under a stub gh: test_free_plan_hint_reads 65 passed in 23.94s; test_adopt_free_plan_hint_root 19 passed in 1.10s; test_no_auto_maintenance 60 passed in 8.32s. Merges: git merge-tree --write-tree of this head with origin/main 5b41d44, #1031 abd1b98, #1030 18cc659, #1041 ce53b0b, #1039 5095ca8 and #1027 a79c957: all clean. Nothing conflicts, so origin/main is not merged in and the base stays e2f9fc4. The PR body has a "Fix round 1 (RV1038)" section with the same. Lane: openreposhape-3 (openRepoShape-3) |
|
|
(lane openRepoShape-3, 2026-10-20T05:41:32Z) RV1038d (independent opus delta review of fix round 1 on #1038, head 3d52f86): READY. No new MUST, SHOULD or NIT. MUST-1 fixed. adopt-project.py:12642-12644 now names the internal root on a plan read as NIT-2 fixed. The PR body credits "which this hint treats as private" to the issue's Direction (b) example; #994's body has NIT-3 fixed. CASES gains only the Mutant B (scripts/family.py:3369, _finish_holder_remote passing "private" for an internal holder), re-made in my own worktree: test_free_plan_hint_reads 1 failed, 64 passed, killed by [family-internal] on the private-subject check. Restored; git diff --quiet HEAD clean. Tests at 3d52f86, one file at a time under a stub gh (exit 97), GH_TOKEN=bogus, empty GH_CONFIG_DIR, GIT_SSH_COMMAND=false, EMAIL set: test_free_plan_hint_reads 65 passed in 25.73s; test_adopt_free_plan_hint_root 19 passed in 1.28s; test_no_auto_maintenance 60 passed in 11.53s. Merge: git merge-tree --write-tree of this head with origin/main 5b41d44 (fetched just now) is clean. Since the base e2f9fc4, main has changed none of this PR's 6 files. Skipped: CI state not read; the scaffold, free_plan_hint, plan_visibility_notes and shape_doctor test files not re-run (the fix round changes a docstring and one test file only). Lane: openreposhape-3 (openRepoShape-3) |
|
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 12 hours by commenting |
… holding U+202E or another character the launcher refuses, by code point and in the launcher's words, before it prints the value or runs `git clone` (#953) (#1043) `setup-project.py`'s self-bootstrap refuses an `$OPENREPOSHAPE_REPO` holding a character the launcher refuses (U+202E and the rest of `MISDRAWN_CHARACTERS`). It refuses by code point, in the launcher's words, before it prints the value or hands it to git. Head `c42afa30`, one commit on origin/main `5b41d44f`. Only `setup-project.py` and a new `tests/test_setup_project_repo_misdrawn.py` change. No flag, no `--help` line and no doc changes. **`setup-project.py`** (section 0, just above `self_bootstrap`): - `MISDRAWN_CHARACTERS` is the launcher's list (`openRepoShape:502` at `5b41d44f`), entry for entry, in the package's tuple form. It is a copy, for `checked_value`'s reason: self-bootstrap mode runs before there is a checkout to read the launcher from. - `misdrawn_characters(value)` names the characters a value holds as `U+XXXX`, in code point order. It is the package's `standard.misdrawn_characters`, and it looks for characters, as `CONTROL_RE` does. - `checked_repo(value)` asks that first. It refuses with the launcher's `checked_repo` sentence, which names the code points and nothing else of the value. Then it calls `checked_value(..., PATH_RE)`, unchanged, for a leading `-` and a control character. - `self_bootstrap` reads a non-empty `$OPENREPOSHAPE_REPO` through `checked_repo`. Unset or empty still means the default. The check stays where `--org` is asked, so the order is the same: `--org` first, then the repository, then `openRepoShape setup` is printed, then `mkdtemp` and `git clone`. - The value's raw bytes are never printed. The refusal goes through `die`, so it is `REFUSED: ...` on stderr with exit 2. **`tests/test_setup_project_repo_misdrawn.py`**, 22 tests: - **Parity:** `test_the_misdrawn_table_is_the_launchers_list` reads `MISDRAWN_CHARACTERS=(...)` out of `openRepoShape` and compares the tuple. It also checks that `misdrawn_characters` names all of the launcher tests' `MISDRAWN`, each once and in order. `test_misdrawn_characters_names_the_launchers_and_nothing_else` runs the launcher's `SHARING_ITS_BYTES`. These follow #947's two tests for the package. - **Direct path, 12 tests:** two runs (`Atlas --org acme`, which is the issue's run, and `--org acme` alone) times six values: U+202E, U+00AD, U+200B, U+E0001 (two, three and four UTF-8 bytes), two characters at once, and a tab before U+202E (the launcher asks for the code point before the control character). - Each test runs a lone copy of the file under `call_loggers`' logging git, gh and curl. - Each checks the launcher tests' `assert_refused_saying`: exit 2, the launcher's sentence, an empty stdout, and no listed character, escape or `forged` in either stream. So no `E2 80 AE` gets out. - The git log must hold exactly one call: the checkout probe. - **Values that still go on, 7 tests:** the launcher's `NOT_CONTROLS` after `acme/`: e-acute, a no-break space, U+200C and U+200D where a script or an emoji needs them, U+2026, U+FF21 and a subdivision flag. Each is printed in the clone line and handed to `git clone` as before. - **Package path, 1 test:** a tree with no `.git` (the package's modules under `src/openreposhape/`, plus `setup-project.py`, `scaffold-project.py`, `shape-doctor.py` and `contracts/repository-naming.yaml`). `openRepoShape Atlas --org acme` is run from that tree's `src` with the issue's value. The package prints its one line (`the standard is the checkout this package sits in, <tree>.`) and hands over. The tree's `setup-project.py` refuses: exit 2, the launcher's sentence, no U+202E and no `forged` in either stream, and only the checkout probe in the git log. - Tests that use the logging stub are skipped on Windows. There `subprocess` starts `git.exe` and never the stub's `.cmd`, so the stub would log nothing. CI is Linux only (#597). - `setup.sh` has no twin of these tests. Its guard covers control characters and the two separators and no other listed character, by #940's design: the launcher refuses the rest first where it is the way in. **"No git call", stated exactly.** The refused run makes one git call, `git -C <dir> rev-parse --show-toplevel`. That is `is_shape_checkout`, which runs before `self_bootstrap` on every run past `--help` and `--preflight`. It asks whether the file stands in a checkout, which is how the run knows it is self-bootstrapping at all, and it names nothing of the value. No git call names the value, and there is no clone. Moving the check above the probe would also refuse the value on the developer path, which never reads it. It would also leave `setup-project.py` ordered differently from `setup.sh`, which probes first and guards only on its self-bootstrap path. These run `$Q/v118/w953/probe.py`. Each case uses a fresh scratch folder, a logging git stub that then runs the real git, a gh that fails, `GH_TOKEN=bogus`, an empty `GH_CONFIG_DIR`, `GIT_SSH_COMMAND=false` and `GIT_CONFIG_GLOBAL=/dev/null`. "Before" is `git archive 5b41d44`; "after" is the worktree. Results are the same under git 2.43.0 and 2.47.1. | Case | Before (`5b41d44f`) | After | |---|---|---| | package, ZIP tree, `Atlas --org acme`, `acme/r[U+202E]forged` | exit 128; `E2 80 AE` on stdout and stderr; git: probe, then `clone --quiet -- acme/r[U+202E]forged /tmp/openreposhape-shape-...` | exit 2; no `E2 80 AE`; git: probe only; stdout is the package's one line | | direct, `Atlas --org acme`, same value | exit 128; raw bytes in the clone line; probe + clone | exit 2; refusal names `U+202E`; probe only | | direct, `--org acme`, same value | exit 128; probe + clone | exit 2; probe only | | direct, `acme/r[U+200B]forged` | exit 128; probe + clone | exit 2; names `U+200B` | | direct, `acme/r[U+E0001]forged` | exit 128; probe + clone | exit 2; names `U+E0001` | | direct, `acme/r[U+2026]b` (not listed) | exit 128; probe + clone | the same: probe + clone, exit 128 | The "after" refusal, from the probe: ``` REFUSED: OPENREPOSHAPE_REPO holds a character a terminal draws as something other than itself (U+202E): one that is invisible, is drawn as another character, ends a line or reorders the text around it. This tool will not put it on a `git clone` command line. Set it to a URL or a path. ``` The new file was run on main's `setup-project.py` (`git show 5b41d44:setup-project.py`, put back byte for byte afterwards): **15 failed, 7 passed**. - Failed: both parity tests (`AttributeError`: no `MISDRAWN_CHARACTERS`, no `misdrawn_characters`), all 12 direct tests, and the package test. - Passed: the 7 `NOT_CONTROLS` tests. They pass at main by design, because they guard against a check that is too broad. Each mutant was applied alone (`$Q/v118/w953/mutate.py`), the new file was run (22 tests), and both files were put back. **All 18 are killed.** | Mutant | Failed | |---|---| | m01 check removed (main's read) | 13 | | m02a check moved after the `(0) self-bootstrap` line | 13 | | m02b check moved after the clone line | 13 | | m03a only the value's first character checked | 15 | | m03b only the table's first entry checked | 13 | | m04 only U+202E checked | 8 (U+00AD, U+200B, U+E0001, two-at-once) | | m05 refusal exits 1 | 13 | | m06a table drops `E0001` | 4 | | m06b parity compares the first five entries + table drops `E0001` | 4 (parity's second assert, `SHARING_ITS_BYTES`, both U+E0001 tests) | | m06c parity compares the first five + table adds `200C` | 2 (`SHARING_ITS_BYTES`, the Persian/U+200C test) | | m06d parity compares the first five + `2060-206F` narrowed to `206E` | 2 | | m06e parity uses set inclusion + table drops `E0001` | 4 | | m07 a range read without its last code point | 15 | | m08 control check asked before the code points | 2 (both tab-then-U+202E tests) | | m09 characters named in the value's order | 3 | | m10 every character past ASCII refused | 7 (every `NOT_CONTROLS` test) | | m11 refusal quotes the value back | 13 | | m12 check skipped on a hand-over (`OPENREPOSHAPE_SELF_BOOTSTRAP=1`) | 1 (the package test alone) | All at `c42afa30`, one pytest process at a time: `/usr/bin/python3 -m pytest -q -p no:cacheprovider -o tmp_path_retention_policy=failed`, one reused basetemp, `EMAIL=service@opensoft.dev`, a stub gh first on PATH, `GH_TOKEN=bogus`, an empty `GH_CONFIG_DIR`, `GIT_SSH_COMMAND=false`, `GIT_CONFIG_GLOBAL=/dev/null`. | Run | git | Files | Result | |---|---|---|---| | h1 | 2.43.0 | `test_setup_project_repo_misdrawn.py` (22), `test_setup_project_ref_parts.py` (27), `test_openreposhape_package.py` (213), `test_readme_windows_quoting_sentence.py` (44), `test_release_install_check_children.py` (30), `test_repo_hygiene.py` (100), `test_agents_md_codes.py` (47), `test_cli_reference.py` (23) | 506 passed in 94.06s | | h2 | 2.43.0 | `test_setup_project_py.py` | 340 passed in 122.68s | | h3 | 2.43.0 | `test_setup_sh.py` | 191 passed in 96.62s | | h4 | 2.47.1 | `test_setup_project_repo_misdrawn.py`, `test_setup_project_ref_parts.py`, `test_setup_project_py.py` | 389 passed in 128.13s | | h5 | 2.43.0 | `test_no_auto_maintenance.py`, `test_setup_project_dry_run_offers.py`, `test_validator_child_encoding.py`, `test_setup_project_name_edges.py`, `test_setup_project_typed_name.py` | 353 passed, 2 skipped in 73.92s (the two skips: `GIT_CONFIG_GLOBAL` was set) | No `shell_quoted` call was added (count 0), so the README's and the handbook's Windows-quoting sentences and `NAMED` are unchanged. `setup-project.py` is not a pinned copy. The wheel takes it at build time (`pyproject.toml` `force-include`), and the tests that read its source (`test_setup_project_py.py`, `test_release_install_check_children.py`, `test_repo_hygiene.py`) pass. - `git merge-tree --write-tree` of `c42afa30` with origin/main `5b41d44f` (fetched with `-c gc.auto=0 -c maintenance.auto=false`) is clean, tree `59de4c261c228274a5cff18aff953211de7002e3`. - No open PR touches `setup-project.py` or the new test file. Every open PR's head was still merged in, and all ten are clean: #1041 `4a23c6e2`, #1039 `5f924500`, #1038 `fd1de5ab`, #1031 `eea70be1`, #1030 `9b4845bb`, #1028 `0c9eac6c`, #1027 `052f1e24`, #1025 `f83ca618`, #1023 `4d0aa1cf`, #954 `dc7fd7ea`. - **RV947's companion is left out.** That companion is the package's scaffold run asking the repository even beside a checkout. - Once this lands, no package run prints the value or hands it to git. Beside a git checkout, the hand-over takes `setup-project.py`'s developer path, which never reads `$OPENREPOSHAPE_REPO`. `self_bootstrap` and `standard.py` are the only shipped Python that read it. Beside a tree with no `.git`, this check refuses it. - The companion would change `standard.prepare`, which `--doctor` and the four tools share. They would then refuse a value they never use. That is the launcher's "checked once, before every run but `--help`" policy, and it is a decision about the package of its own. - `src/openreposhape/standard.py` is also open in #1038. - **Found while measuring, not changed here:** `CONTROL_RE` and `PATH_RE` hold `\x00-\x1f\x7f` only, so a C1 control passes `checked_value`. The launcher and the package refuse C1 (U+0080 to U+009F) in a repository. With `acme/r[U+009B]31mforged`, the clone line prints the raw `C2 9B`, before and after this change. `--into` and `--local-remote-dir` go through the same `PATH_RE` (read in the code, not measured). That would be a follow-up for `checked_value`, which every flag shares. Review: rv953, lane 5's independent reviewer, not the writer (w953): READY at `c42afa30`, the head that lands (issuecomment-6284626889), with no MUST. It rules #953's Verify met in substance: the refused run's only git call is the checkout probe every run makes, which names nothing of the value, and there is no clone. Its SHOULD is #459's documented C1 known limit (U031, L4-24), left with its test NIT for a follow-up on a ruling. CI (Brett Heap's ruling 27): H0 is the head c42afa3 itself, which passed tests, tests-git-bash and SonarCloud; main has touched none of its files since its base 5b41d44; and the local gate passed on its predicted squash tree e146bb5 over main e656199: the setup_project_repo_misdrawn, repo_hygiene, agents_md_codes, cli_reference sets, 192 passed. Landed on Brett Heap's word of 2026-10-18T14:45:28Z, relayed to every lane by lane 3 (#473 issuecomment-6251255596): "merge v1.2.0 PRs once reviewed and green". Closes #953. Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hain link before an `=` that leaves a path after it, so the offer the gate prints for a link holding `=` verifies that link when typed as printed (#980) (#1044) For a declared-unverified chain link whose name holds `=`, the naming gate offers only sources that verify that link's tree when typed as printed. The gate offered `open=y` the source `--link-source open=y=<path>`, and read every `--link-source` key at the value's first `=`: the key was `open`, which no chain names, so the value was a bare PATH for the first link, `open=y` stayed declared-unverified under the same offer, exit 0. **The issue's direction (a)**, in `scripts/validate-repository-naming.py`. `LinkSources._read_source` reads a `--link-source` key the way `_read_remedy` reads a `--link-remedy` link since #949: the longest text before an `=` that names a link of a chain this run reads, by `pin_product_key`, and leaves a path after it. `__init__` and `no_path` both take the key and the path from it, so the path refused as empty is the one after the key's own `=`. For a chain whose links hold no `=`, every value is read exactly as before: a text holding `=` can only name a link that holds one. **What (a) keeps from the documented rules, and how each case was checked** (probes below, and one test row or more each): - A link holding `=`: `open=y=<tree>` verifies `open=y`, as printed and in capitals (`OPEN=Y=<tree>`, by `pin_product_key` as every key is). - Two links where one is a prefix of the other (`open` and `open=y` in one chain): `open=y=<tree>` is `open=y`'s and `open=<tree>` is `open`'s, in either order. On main both were `open`'s and the later replaced the earlier, so `open=y` read none. - A path that itself holds `=`: `open=y=<dir>/a=b/tree` is `open=y`'s with that whole path. A text holding `/` names no link, since a link's name is `repo_basename`'d, so an `=` after a `/` never ends the key. A path that begins like a longer link's name is given after `./`: `open=./y=dir` is `open`'s in a chain naming `open=y` too. - A key that names a link but leaves an empty path: `open=y=` is `PRODUCT=` for `open=y`, refused as `link-source-no-path`, exit 2, as `openXdox=` is (#442). On main it was a bare PATH asked for `openXdox`, a link the person did not name. Where a shorter link leaves a path (`open=y=` with `open` in the chain too), that link is the key, `open` with the path `y=`, as `--link-remedy openXdox=y=` gives `openXdox` the words `y=` where a chain names `openXdox=y`: the longest link that leaves a path wins, and `open=y` leaves none. - A value that is a real directory spelled like the whole value: `open=y=x` beside a directory `open=y=x` is still `open=y`'s, decided by the chain and not the disk, with `_also_a_directory`'s line; `./open=y=x` asks for the directory, as a bare PATH for the first link. - The key is read whole, where a remedy's link is stripped and cut at its last `/`: ` open=y=<tree>` and `opensoft/open=y=<tree>` name no link and stay bare PATHs, as on main. **One difference from `--link-remedy`, deliberately.** `--link-remedy open=y=` is a NOTE (its link falls back to the first `=`, `open`). `--link-source open=y=` is refused, because the #442 rule refuses `PRODUCT=` for every link, and `--link-remedy`'s own `openXdox=` refusal is made by argparse before the chains are known, which is why it cannot catch `open=y=`. `_read_source`'s docstring says so. **Not (b).** (a) makes the offer the gate already prints true, so the person who types it gets the verification; (b) would only stop offering it. Setup's step (4) is unchanged: it passes the gate its `--pin-source` as an absolute path, with a separator before every `=`, so no text before one names a link. **Docs.** The module docstring (a new paragraph, "A KEY IS THE LONGEST LINK A VALUE NAMES"), the `LinkSources`, `_link_remedy` and `_read_remedy` docstrings, the `--link-source` help, and `docs/cli.md` regenerated from it. `setup-project.py`'s `as_the_plan_reads_it` docstring stated the first-`=` rule, and now states this one. README, the handbook and AGENTS.md state no rule for how a `--link-source` key is read (README names `--link-source PRODUCT=<path>` only), so they are unchanged. The class docstring records that `validate-pins.py` still ends a `--pin-source` key at the first `=`; no scaffold writes a pin holding one (`--pin` names are `[A-Za-z0-9_.-]`), and it is left as its own change. Measured from a root scaffolded at main `5b41d44f` (local remotes, no network): - `update-shape.py check --upstream <this checkout> --at c64e85f` lists `upstream-changed scripts/validate-repository-naming.py`, with `upstream-changed 1 unchanged 10`. - `update-shape.py apply --yes --branch ...` exits 0, commits `contracts/shape-pin.yaml`, `project.yaml` and the copy, and the project's validators print `pins ok` and `manifest ok`. A fresh `check` prints `unchanged 11`, and the root's own naming gate exits 0. - The new sha256 of `scripts/validate-repository-naming.py`: `ec167fb0e259f90705db895a1a37f4adebd4cbf03ca2d5ae18f6d9a8df896548` (78664 bytes). No other pinned copy and no template changes. Run from empty folders, `<D>` a fresh folder, trees as named (`trees/y` declares `openInk`, `trees/x` declares `open=y`; for the chain with `open`, `trees/x2` declares `open` and `trees/o` declares `open=y`). The gate prints an arrow between links; it is written `->` here. | Probe | At main `5b41d44f` | At head `c64e85fe` | |---|---|---| | P1, the issue's row: `--referent-chain openXdox,open=y,openInk --link-source open=y=<D>/trees/y` | `link-source-unreadable ... could not answer for openXdox`, "It was read as a PATH: `open` is not a link", and `open=y` declared-unverified with the same offer; exit 0 | only `openXdox` declared-unverified (no source named it); no `link-source-unreadable`; exit 0 | | P2, P1 plus `openXdox=<D>/trees/x`, `--explain` | NOTE: the value not used, read as a PATH; `CHAIN openXdox -> open=y -> openInk [declared-unverified]` | `[verified]`, nothing warned or noted | | P3, chain `openXdox,open,open=y,openInk`, `open=<o>` then `open=y=<y>` | `link-source-unreadable: y=<D>/trees/y (--link-source open=...)`, NOTE `open=<o>` replaced; 2 links declared-unverified | `[verified]`, nothing warned or noted | | P4, `open=y=` (chain without `open`) | bare PATH `open=y=` asked for `openXdox`; exit 0 | `REFUSED link-source-no-path: --link-source open=y= names no path for open=y`; exit 2 | | P5, `open=y=` (chain with `open`) | `y= (--link-source open=...) could not answer for open` | the same | | P6, `open=y=<D>/trees/a=b/y` | read as a PATH, `[declared-unverified]` | `[verified]` | | P7, `open=y=x` beside a directory `open=y=x` | bare PATH, read as the directory for `openXdox` | `link-source-unreadable: x (--link-source open=y=...) could not answer for open=y`, then "`open=y=x` is also a directory; `--link-source ./open=y=x` reads it as one." | | P7b, `./open=y=x` | the directory, for `openXdox` | the same | | P8, chain with `open`: `open=./y=dir`, `open=y=<y>` | `open=y=<y>` read for `open`, replacing `open=./y=dir` | `[verified]` | Eleven new rows in `tests/test_chain_remedy_callers.py`, section "#980", reusing its helpers (`naming`, `tree`, `gate_warnings`, `UNREACHED`, `GATE_OFFER`, `EQUALS`), with no assert that joins conditions with `and`: - `test_the_offer_for_a_link_holding_an_equals_verifies_it[as-printed, in-capitals, a-path-holding-an-equals]`: reads the offer the gate prints for `open=y`, types it with a tree, and asserts the one WARNING left is `openXdox`'s, with no `link-source-unreadable` and no NOTE. - `test_each_link_reads_its_own_source_where_one_name_begins_another[open-first, open=y-first, a-path-that-begins-like-the-longer-link]` - `test_a_value_that_ends_at_a_link_holding_an_equals_names_no_path` - `test_a_shorter_link_that_leaves_a_path_is_the_key` - `test_a_link_holding_an_equals_wins_over_a_directory_spelled_like_it` - `test_a_key_is_read_whole[a-blank, an-owner]` **Failing at main.** The test file copied onto a `git archive` of `5b41d44f`: 8 of the 11 fail, each on the defect (the offer row's `open=y` WARNING and `link-source-unreadable`, the prefix rows' `[declared-unverified]`, `open=y=` exiting 0, the keyed reading not winning over the directory). The other 3 pass at main by design: they pin behaviour main already had (`test_a_shorter_link_that_leaves_a_path_is_the_key`, `test_a_key_is_read_whole[a-blank]` and `[an-owner]`), and each kills a mutant below. **Mutants**, each applied alone to the fixed gate in a scratch repository, then `test_chain_remedy_callers.py`, `test_link_source_chain_defaults.py`, `test_naming_policy.py`, `test_chain_link_fold.py` and `test_chain_pin_sources.py` (400 passed, 4 skipped with no mutant). All 11 killed: | Mutant | Failed | Killed by (new rows unless named) | |---|---|---| | M1 back to `typed.partition("=")` | 8 | the offer rows, the prefix rows, `open=y=`, the directory row | | M2 the shortest match instead of the longest | 3 | the prefix rows | | M3 the "leaves a path after it" check dropped | 1 | `test_a_shorter_link_that_leaves_a_path_is_the_key` | | M4a the `./PATH` rule dropped (`./` stripped from the key) | 3 | the directory row; `test_a_labelled_source_holding_an_equals_is_not_said_to_be_a_path`; `test_naming_policy.py::test_a_link_named_before_the_equals_is_the_key_even_over_a_directory` | | M4b the key cut at its last `/`, as a remedy's link is | 4 | as M4a, and `test_a_key_is_read_whole[an-owner]` | | M5 the empty-path fallback dropped | 6 | `open=y=`; `test_a_value_that_names_no_path_is_spelled`; 4 rows of `test_link_source_chain_defaults.py::test_a_link_source_that_names_no_path_is_refused` | | M6 the fallback at the first `=`, as a remedy's | 1 | `open=y=` | | M7 `no_path` taken after the first `=` | 1 | `open=y=` | | M8 the keyed path taken after the first `=` | 7 | the offer rows, the prefix rows, the directory row | | M10 the key stripped, as a remedy's link is | 1 | `test_a_key_is_read_whole[a-blank]` | | M12 the key not folded by `pin_product_key` | 56 | `in-capitals` and the prefix rows, and 52 existing rows | **Runs**, all on head `c64e85fe`, `/usr/bin/python3 -m pytest -q -p no:cacheprovider -o tmp_path_retention_policy=failed`, one reused basetemp per run, with `EMAIL=service@opensoft.dev`, a stub `gh` first on PATH, `GH_TOKEN=bogus`, an empty `GH_CONFIG_DIR`, `GIT_SSH_COMMAND=false` and `GIT_CONFIG_GLOBAL=/dev/null`: - git 2.43.0 (`/usr/bin/git`), 31 files: the 25 that run the naming gate (`grep -l validate-repository-naming tests/`, the pinned-copy files `test_pinned_copy_text.py`, `test_update_shape_kept_helper.py`, `test_update_shape_unreadable.py` and `test_adopt_reencoded_files.py` among them, and `test_repo_hygiene.py`), the files that scan every script's source (`test_git_output_decoding.py`, `test_shape_advisory.py`, `test_no_auto_maintenance.py`, `test_non_utf8_git_failures.py`), and the trio's other two, `test_agents_md_codes.py` and `test_cli_reference.py`: **2964 passed, 5 skipped** in 1749 s, exit 0. - git 2.47.1 first on PATH, the 25 naming-gate files: **2680 passed, 3 skipped** in 1181 s, exit 0. No template changes. `grep` of `tests/` found no fixed digest or `^id:`-style pattern over the gate: the tests that read a root's copy of it (`test_shape_doctor.py`, `test_doctor_validator_verdicts.py`) compare it at run time, and both are in the first run, as are the source scanners above and `test_cli_reference.py`, which holds `docs/cli.md` to the help. - `git merge-tree --write-tree origin/main HEAD`, origin/main `5b41d44f` (fetched with `-c gc.auto=0 -c maintenance.auto=false`, and `git ls-remote` agrees): clean, tree `82780dc63d7f`, the head's own tree. - Open pull requests: none of the 9 open (#954, #1023, #1025, #1027, #1028, #1030, #1031, #1038, #1039) touches `scripts/validate-repository-naming.py`, `tests/test_chain_remedy_callers.py`, `setup-project.py` or `docs/cli.md`, so there is no merge-tree to run against them. Review: rv980, lane 5's independent reviewer, not the writer (w980): READY at `c64e85fe` (issuecomment-6288391970), with no MUST or SHOULD; the landing head only merges main over it. Its NIT-1 (three test rows for the corners of the old first-`=` split) and NIT-2 (a sentence in `validate-pins.py`) go to a follow-up. CI (Brett Heap's ruling 27 and his fast-refresh ruling of 2026-10-15T22:48:35Z): H0 c64e85f passed tests, tests-git-bash and SonarCloud; the head 0f5845b is a clean update-branch merge of main b0babe1, with tests-git-bash and SonarCloud green; and the local gate passed on its predicted squash tree ac9c882 over main b0babe1: the chain_remedy_callers, repo_hygiene, agents_md_codes, cli_reference sets, 233 passed. Landed on Brett Heap's word of 2026-10-18T14:45:28Z, relayed to every lane by lane 3 (#473 issuecomment-6251255596): "merge v1.2.0 PRs once reviewed and green". Closes #980. Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…makefile` or `GNUmakefile` GNU make reads first, and where a case-blind disk writes the shape's; README says which Makefile exports `CONTRACTS_DIR` (#977) (#1046) ## What Two follow-ups to #906 that lane 1's review of #964 (r906) found and ruled out of that pull request. Neither is a regression: both were as untrue on `main` before #906 as after it. **(a) The Makefile follow-up names the root file GNU make reads first.** The line is in `plan`'s `follow_ups:`, in `check`'s output and in the split commit's message, which is also the pull request's body. GNU make reads the first file it finds of `GNUmakefile`, `makefile` and `Makefile`, and only that one. #906 asked only whether the source keeps `Makefile`, comparing names byte for byte (`_root_keeps`). So a source that keeps `makefile` or `GNUmakefile` got "the root Makefile wires `CONTRACTS_DIR` ...". But the clone's root then holds the shape's `Makefile`, with the block, and the source's file, which `make` reads instead, so `make contracts-dir` answered `No rule to make target 'contracts-dir'` (r906's probe, both names). `_makefile_follow_up` now writes the line: - It names the source's file that GNU make reads at the root, wherever that file is not the `Makefile` #906's words already name. It also says what it takes for `make` there to export `CONTRACTS_DIR`. - On a disk that ignores case, a kept `makefile` IS `Makefile`. `place` (`Path.exists()`) finds the name taken and writes the shape's Makefile as `shape/Makefile`. The line says so in `CASE_ASIDE`'s words. `_root_keeps` gains `fold`, which asks the question the way `_KeptTree` asks it for `check`'s disk that ignores case. - A source that keeps only `Makefile`, or none of the three names and no other case of `Makefile` that a rule keeps, gets the line it got before, byte for byte. - Each kept name is printed with `_spelled`, and the three names GNU make looks for with `_and_listed`. No `shell_quoted` call is added. | The root keeps | Before | After | | --- | --- | --- | | none | "the root Makefile wires `CONTRACTS_DIR` ..." | unchanged | | `Makefile` | #906's line: "the shape's Makefile wires ...; but the source keeps a Makefile of its own at the root, so that block waits in `shape/Makefile` until the two are merged, and the run adds none of it to the root's Makefile" | unchanged | | `makefile` | "the root Makefile wires ..." | "the shape's Makefile wires ...; it is written as the root's `Makefile`, or as `shape/Makefile` once case is ignored, as a macOS or Windows disk does, where the source's `makefile` is that path, until the two are merged; and GNU make at the root reads the source's `makefile`, ..." | | `GNUmakefile` | "the root Makefile wires ..." | "the shape's Makefile wires ...; it is written as the root's `Makefile`, but GNU make at the root reads the source's `GNUmakefile`, ..." | | `makefile` and `GNUmakefile` | "the root Makefile wires ..." | as for `makefile`, and GNU make reads the source's `GNUmakefile` | | `Makefile` and `makefile` | #906's line | #906's line, then "; and GNU make at the root reads the source's `makefile`, ..." | | `Makefile` and `GNUmakefile`, or all three | #906's line | #906's line, then "; and GNU make at the root reads the source's `GNUmakefile`, ..." | | `MAKEFILE`, kept in the root by a `--path-policy` rule (with the shipped rules `plan` asks about it, and a person's `root` answer keeps the line `plan` wrote: "the root Makefile wires ...") | "the root Makefile wires ..." | "the shape's Makefile wires ...; it is written as the root's `Makefile`, or as `shape/Makefile` once case is ignored, ..., where the source's `MAKEFILE` is that path, until the two are merged" | The clause that names the file, in full: ```text GNU make at the root reads the source's `makefile`, the first of `GNUmakefile`, `makefile` and `Makefile` it finds, so `make` there exports `CONTRACTS_DIR` only once `makefile` does, or includes a Makefile that does ``` **(b) README.md, section "Adopting an existing repository".** - Before: "the root Makefile it materializes exports `CONTRACTS_DIR`, pointing at the `contracts/` of the spec leg where the plan mounts it (`$(CURDIR)/spec/contracts` by default)." - After: "the shape's Makefile exports `CONTRACTS_DIR`, pointing at the `contracts/` of the spec leg where the plan mounts it (`$(CURDIR)/spec/contracts` by default); it is the root's Makefile, or, where the source keeps a `Makefile` of its own, `shape/Makefile`, which holds the export until a person merges the two." This is r906's wording, as its re-check corrected it, with one change: `Makefile` is in backticks in "a `Makefile` of its own". A source that keeps `makefile` has the shape's Makefile written as the root's `Makefile` on a disk that keeps case, so the plain word would have claimed `shape/Makefile` for it. The section is bound to the handbook's `adopt` section. The handbook says nothing of `CONTRACTS_DIR`, and its sentence that a shape file whose name the source already holds, `Makefile` among them, is written under `shape/` stays true. So only the section's marker moves, to the section's new blob, computed with `readme_segments` and `readme_fingerprint`. The tail of the paragraph is reflowed, and README.md grows by one line, to 1479, which is the cap. No `tests/readme-cap.d/` file is needed while no other README pull request lands first. ## Measured On a disk that keeps case (Linux, GNU Make 4.3, git 2.43), each row ran a real `execute --local-remote-dir` and made a recursive clone of the split. Each file the source keeps has its own `which` target, so `make which` at the clone's root prints the name of the file GNU make read. | The root keeps | `make which` prints | the split message names | `make contracts-dir` at the root | the block is in (`make -f <it> contracts-dir` prints the spec leg's `contracts/`) | | --- | --- | --- | --- | --- | | `makefile` | `makefile` | `makefile` | no rule | `Makefile` | | `GNUmakefile` | `GNUmakefile` | `GNUmakefile` | no rule | `Makefile` | | `makefile` and `GNUmakefile` | `GNUmakefile` | `GNUmakefile` | no rule | `Makefile` | | `Makefile` | `Makefile` | #906's line, the source's own `Makefile` | no rule | `shape/Makefile` | | `Makefile` and `makefile` | `makefile` | `makefile` | no rule | `shape/Makefile` | | `Makefile` and `GNUmakefile` | `GNUmakefile` | `GNUmakefile` | no rule | `shape/Makefile` | | all three | `GNUmakefile` | `GNUmakefile` | no rule | `shape/Makefile` | The disk that ignores case is not measured: there is none here. What the line says of it rests on `place`'s `Path.exists()`, and is tested against `_KeptTree(..., fold=True)`, `check`'s model of that disk. ## Tests - `tests/test_adopt_make_reads_first.py`, new: 33 tests. - The line word for word, for every row above, and for `makefile` and `GNUmakefile` sent to legs. - The line against `_KeptTree` on both disks. - `plan` and `check` for each row but none and `Makefile`, which `test_adopt_contracts_dir` and `test_adopt_printed_sentences` already plan and check. - A real `execute` for each row except none, with the `make` measurements above. - README's sentence, and the handbook marker matching the section as it now stands. - Fail on base: on `e2f9fc42` (main when the branch was made), 23 fail and 10 pass. The 10 are the handbook marker test, which was current on base, and rows whose line does not change: none and `Makefile` alone; `makefile` and `GNUmakefile` sent to legs; and the `Makefile`-plus-another rows of the disk test, which asks only where the copy goes. - Gate on this head, named files only, one basetemp, a stub `gh` first on PATH, `GH_TOKEN=bogus`, `GIT_SSH_COMMAND=false`, `GIT_CONFIG_GLOBAL=/dev/null`: - The new file, `test_adopt_contracts_dir`, `test_adopt_printed_sentences`, `test_adopt_leg_keys`, `test_adopt_plan`, `test_adopt_e2e`, `test_repo_hygiene`, `test_readme_sections`, `test_agents_md_codes`, `test_cli_reference`, `test_readme_windows_quoting_sentence` and `test_scaffold_leg_path_collisions`: 652 passed, 1 skipped (the MedxEHR test, no clone here). - Every other adopt test file that names `Makefile`, `makefile` or `CONTRACTS_DIR`: `attributes_eol_block`, `verify`, `ignored_shape_files`, `leg_path_collisions`, `submodules`: 368 passed. `root_manifest`, `root_symlinks`, `symlink_target_bytes`, `root_shape_paths`: 403 passed. - `test_shape_doctor.py` in 4 shards: 445 passed, 3 skipped (the PowerShell line, Windows only). - After the last test-only edit (helpers imported from `test_adopt_printed_sentences`): the new file and `test_adopt_printed_sentences`, 87 passed (33 and 54). Mutants of the change, each restored. Each one is killed: | Mutant | Failed | | --- | --- | | GNU make's order with `makefile` before `GNUmakefile` | 18 of 33. The end-to-end rows `makefile`+`GNUmakefile` and all three fail on `make which` against the split message itself. | | the case-folding check off | 9 | | the joining word always "; and" | 3 | | "the shape's" not chosen when only `GNUmakefile` or `makefile` is kept | 4 | | GNU make's first file never found | 19 | | `_root_keeps` folds case by default | 13, #906's `the-root-keeps-it` row among them | | README's old sentence | 3, `test_the_handbook_follows_the_readmes_outline` among them | | the handbook marker left stale | 2 | ## Merge with main `git merge-tree --write-tree` of this head with `main` at `dc7cd49b` is clean (tree `ae9abfcb`). Of this branch's files, only `adopt-project.py` moved on main: #1030 (#900) changed `_spelled`, and #1038 (#994) changed a hint and a docstring. `_spelled` changes only a backslash, a double quote or a backtick inside a name. The names this line spells are plain letters, so its words do not change. README.md, `docs/handbook.html` and `tests/readme-cap.d/` did not move on main, so the marker stays current and the cap still holds. On the merge, built in a `git clone --shared` checkout: the new file, `test_adopt_printed_sentences`, `test_repo_hygiene` and `test_readme_sections`, 263 passed. ## Not in this PR (follow-ups for lane 1 to file) - **NEXT's `make bootstrap`, measured.** For a source that keeps `makefile` or `GNUmakefile`, `execute`'s NEXT block prints `make bootstrap`. `make -n bootstrap` in the clone exits 2 with `No rule to make target 'bootstrap'`. For `Makefile` and `GNUmakefile`, its NOTE says the shape's make targets "arrive only once the follow-up merges the two". But GNU make at the root reads `GNUmakefile`, not the merged `Makefile` (inferred; before the merge, `make -n bootstrap` there exits 2, as the NOTE says). - **`collision_follow_up`'s sentence.** In `shape_materialize.py`, "The shape's make targets ... arrive only with that merge" is likely untrue where the source also keeps `GNUmakefile` or `makefile`, which GNU make reads before the merged `Makefile`. Inferred, not measured. - **`AGENTS-shape.md` in the pinned template.** "Where the source kept a Makefile of its own, that block waits in `shape/Makefile`" has the same gap for a kept `makefile`. It is pinned bytes, so it is left. - **Disks that ignore case.** - `_predict_collisions` stays byte for byte, so on such a disk a plan for a kept `makefile` lists no "merge shape/Makefile into Makefile" follow-up. `execute`'s own collision report lists it. - A split made on a disk that keeps case commits both `Makefile` and `makefile`, two names that collide in a clone on a disk that ignores case. - Spellings such as `gnumakefile` are not asked about. - None of this is measured. **Review round 0, body only** (lane 1's reviewer r1046's partial review, issuecomment-6288714370): the `MAKEFILE` row and the byte-for-byte sentence now say that a `--path-policy` rule, not a person's answer, is what keeps `MAKEFILE` in the root. Follow-ups are built from the rules' answers alone, so a `MAKEFILE` a person answers `root` keeps "the root Makefile wires ...", which a disk that ignores case makes untrue, as on base. **Review round 1** (`c121ed90`, test file only): the comment above `ANSWERED` says a `--path-policy` rule, not a person's answer, reaches the follow-ups (r1046's SHOULD-1). The new test file is ASCII: its section signs and em dashes are written as escapes, with two lines rewrapped: the assert's string is unchanged, the `re` pattern matches the same text (its raw string now holds the escape), and one docstring changes only where its lines break (NIT-2). `tests/test_adopt_make_reads_first.py` 33, `tests/test_adopt_printed_sentences.py` 54 and `tests/test_repo_hygiene.py` 100 passed. It lands by fast refresh (Brett Heap, 2026-10-15T22:48:35Z). Its reviewed head `c121ed90` is green on Linux and SonarCloud. The branch merged main once and was never rebased; the last merge is update-branch's, clean, giving db92198, which is green on SonarCloud and Git Bash. Lane 1's gate passed on its tree: the 13 test files that run `adopt-project.py`'s Makefile follow-ups and `make`, read README and the handbook, the doctor and the docs, 1,097 passed and 4 skipped. Closes #977 Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>



Three small statements in the source are made true (v1.2.0, issue #994, from RV969's NITs 3 and 6 and RV947's round-2 NIT-6). Only item 2 changes behaviour.
What changed, and why
adopt-project.py,_free_plan_hint_by_the_root's docstring (RV969 NIT 3). It said the words adopt printed before "are unchanged". Since scaffold-project.py,family.py initand shape_materialize.py: the plan names the tracking branch, and on disk the visibility; an unread root or reused leg is said to be unread, the root before the plan; every holder gets the members' NOTE (#913) #969 (scaffold-project.py,family.py initand shape_materialize.py: the plan and the visibility NOTEs go by values GitHub did not give, misword an internal root, skip a public holder's credential NOTE, and name no tracking branch #913, L2-93) an INTERNAL root whose plan is unread is toldFREE_PLAN_UNREAD_INTERNAL, notFREE_PLAN_UNREAD, and since adopt-project.py, shape_materialize.py and standard.py: a docstring says adopt's Free-plan words are unchanged where an internal root's note changed, an internal root on a Free plan is told it is private, and a docstring line is 104 characters #994 (item 2) an internal root on a plan read asfreeis told it is internal, which the hint treats as private. The sentence now says both, and that every other case is in the words adopt printed before.grep -n "words adopt printed before are unchanged" adopt-project.pyfinds nothing.scripts/shape_materialize.py,free_plan_secrets_note(RV969 NIT 6). An internal root on a plan read asfreewas told "acme/Atlas is private". I took the issue's Direction (b): thewhatargument the function already passes toFREE_PLAN_HINTnow says "{root} is internal, which this hint treats as" for an internal root, so the hint reads "acme/Atlas is internal, which this hint treats as private, so org-level SHAPE_LEGS_APP_ID / ...". Every other root is passed "{root} is" as before.scripts/repo_shape.py'sFREE_PLAN_HINT(a pinned copy) is not touched, so no project re-pins. The function's docstring says so, and a comment at the branch says why the case should not arise (GitHub gives internal visibility only to an enterprise-owned organisation, which is not on Free) and why the hint still holds of it if it does.src/openreposhape/standard.py,prepare's docstring (RV947 NIT-6). The paragraph is re-wrapped at 75 characters with no word changed (textwrap, and the words were compared before and after).awk 'length($0) > 100' src/openreposhape/standard.pyprints nothing; the line that was 104 characters is gone.Tests, one row each for item 2:
tests/test_free_plan_hint_reads.py::test_the_note_goes_by_both_answers[internal-free]pins the exact words and that "is private" is absent.tests/test_adopt_free_plan_hint_root.py::test_a_closed_root_gets_the_hint_naming_the_root[INTERNAL](the parameter ids are unchanged) pins adopt's printed hint, and the[PRIVATE]row now also asserts the internal words are absent.tests/test_scaffold_reuse_seeded_legs.py::test_the_free_plan_hint_goes_by_the_root_as_it_stands[internal-root]pins the scaffold's, and every row now asserts "{root} is private, so" is printed exactly where the root is private.Left out of scope
Nothing in #994 is out of scope; the issue has no "Needs a ruling" or "Documented limit" section. Not done: the other four lines in
standard.pyover 79 characters (81 characters,:181,:339,:375,:426), which the issue names and leaves; the tests count no line length.Tests (at the first head,
73ad80d5e)Each file alone, under a stub
gh(exit 97),GH_TOKEN=bogus, an emptyGH_CONFIG_DIR,EMAILset,/usr/bin/python3 -m pytest -q -p no:cacheprovider:Mutants
whatisf"{root} is"again, for every root): 3 failed, 142 passed over the three files. Killed bytest_free_plan_hint_reads.py::test_the_note_goes_by_both_answers[internal-free],test_adopt_free_plan_hint_root.py::test_a_closed_root_gets_the_hint_naming_the_root[INTERNAL]andtest_scaffold_reuse_seeded_legs.py::test_the_free_plan_hint_goes_by_the_root_as_it_stands[internal-root].if visibility else): 6 failed, 75 passed over two files. Killed by theprivaterows, among themtest_the_note_goes_by_both_answers[private-free]andtest_a_closed_root_gets_the_hint_naming_the_root[PRIVATE].family.py initpassing "private" for an internal holder: killed bytest_free_plan_hint_reads.py::test_each_tool_says_what_github_said_of_the_root_and_its_plan[family-internal]; it survived before theinternalcase.Merges (at the first head,
73ad80d5e)git merge-tree --write-treeof that head with origin/main5b41d44fc: clean (this branch was cut ate2f9fc424, one commit behind; that commit changesvalidate-family.pyonly). With #1031 (abd1b9844): clean. With #1030 (18cc65939): clean. Those two are the open PRs that touchadopt-project.py; none touchesshape_materialize.py,standard.pyor these tests.Fix round 1 (RV1038)
RV1038 found it NOT READY on one MUST, a docstring. Head
3d52f86ddc8ca920bdbd172b02ed4ebee72e1510, one commit over73ad80d5e; it changes a docstring and one test file, and no code that runs.adopt-project.py. The new sentence said "every other case is in the words adopt printed before", which item 2 itself made false for an internal root on a plan read asfree(it was told "is private", and is now told "is internal, which this hint treats as private"). It names that case now, in RV1038's words.grep -n "words adopt printed before are unchanged" adopt-project.pystill finds nothing.CASESintests/test_free_plan_hint_reads.pyhas aninternalcase (an INTERNAL root on a Free plan), so the scaffold, adopt andfamily.py initare each pinned for the hint's words about it. The test's hint check is now two checks: the hint's other lines appear for either closed visibility, and the subject line is the private one or the internal one, never both. RV1038's mutant B (scripts/family.py's_finish_holder_remotepassing "private" for an internal holder) survived the 62 rows before and is killed now:test_each_tool_says_what_github_said_of_the_root_and_its_plan[family-internal]fails, 1 failed and 64 passed. The file was restored exactly (git diff HEAD -- scripts/family.pyis empty).repo_shape.pyis a pinned copy.gh:git merge-tree --write-tree: origin/main5b41d44fcclean; adopt-project.py:check's throwaway repositories are made in the work directory with the source's object format and no hook of this machine's; a path git refuses isplan-path-git-refuses, a repository git cannot make thereadopt-git-unasked(#910) #1031abd1b9844clean; adopt-project.py: the split commit's message andplan's report name each path, question and rule id one way only and never raw, the history scan'sgit-failedis one line, and the table pads a name as stdout prints it (#900) #103018cc65939clean; adopt-project.py:plan --project NUL(andCon,com1,Lpt9) is refused asadopt-bad-project-namebefore anything exists, andcheckandexecutefind it in the plan'slegs.assembly, by the one question the scaffold andfamily.py initask (#1014) #1041ce53b0b4fclean; family.py and tests:Family.rewritten's refusal of twomembers:lines is asked of the method again, its docstring says no verb reaches that count since #974, and a refusal that leaves a ref behind failsassert_unchanged(#1029) #10395095ca889clean; family.py:bump's fetch leaves out every refspec of the mount's whose source can name a tag, a pattern such asrefs/*and a short name too, so a mirror in a folder,+refs/*:refs/remotes/origin/all/*, no longer fails the fetch on a branch and a tag of one name or pins a commit only a tag reaches (#1009) #1027a79c957b0clean. Nothing conflicts, soorigin/mainis not merged in and the base stayse2f9fc424.Closes #994
Lane: openreposhape-3 (openRepoShape-3)
🤖 Generated with Claude Code