Repository navigation
family.py: add refuses a --member whose owner or project does not begin with a letter or a digit as member-malformed before the member is read, so testorg/_Zed, testorg/.Zed, testorg/-Zed, _org/Zed and .org/Zed no longer exit 0 with a commit that the holder's own validate-family.py fails as member-repository (#366) - #399
Merged
Merged
Conversation
… begin with a letter or a digit as `member-malformed` before the member is read, so `testorg/_Zed`, `testorg/.Zed`, `testorg/-Zed`, `_org/Zed` and `.org/Zed` no longer exit 0 with a commit that the holder's own `validate-family.py` fails as `member-repository` (#366) `MEMBER_RE` and the validator's `QUALIFIED_RE` shared one character class; the validator also requires each segment's first character to be a letter or a digit. `add` writes `--member` verbatim as the row's `repository:`, so a name the looser pattern admitted was mounted, pinned and committed with exit 0, and the gate the holder ships then failed the commit `add` had just reported. Two tools of this standard disagreed about one rule, and the one that writes the file was the looser. `MEMBER_RE` now takes `QUALIFIED_RE`'s shape. The rulings: 1. THE `.`/`..` CLAUSE IS KEPT, AND MOVED FIRST. The pattern would refuse `<org>/..` too, for beginning with a dot, but in its general sentence; what is wrong with `.` and `..` is that they are no name at all, and #299's more specific sentence says that. It is read before the pattern, so it is still reachable, and only on a name with a `/` (a bare `.` has no owner to blame, and the general sentence asks for one). Its remedy no longer offers `.github` as the example of a name with a dot, since `.github` is refused now, by the validator too; it says `Ze.d`. 2. ONE RULE, PINNED, NOT MOVED. `repo_shape.py` is a copied file: a constant moved there would reach an existing holder only through `update-shape.py`, and both validators spell `QUALIFIED_RE` themselves and ship into projects that run them from their own copy. So `family.py` spells it a third time, and `tests/test_family_member_name.py` reads every spelling (the source, never running the validators: it finds `MEMBER_RE` and every tracked module's `QUALIFIED_RE` outside `tests/`, so a fourth copy is pinned the day it is written) and fails when they are not the same pattern. A corpus of names says what that pattern means, so a change that keeps the spellings equal and moves the rule has to move the corpus too. 3. THE VALIDATOR'S RULE IS UNCHANGED. Which names GitHub accepts is not claimed; `testorg/.github` was refused by the validator already, so nothing a holder can keep depended on `add` admitting it. The general sentence now says the rule (each name begins with a letter or a digit, then letters, digits, `.`, `_`, `-`) and its remedy says why: a commit that wrote such a name would fail the gate the holder ships. BEHAVIOUR THAT MOVES BESIDE THE FIVE. `--member=-org/Zed`, the one way a leading `-` reaches the owner, passed the old pattern and was refused later by `checked_value` as `unsafe-value`; the pattern is asked first, so it is `member-malformed` now (exit 2, nothing read, either way). `testorg/.github` and `testorg/...` are refused. `tests/test_family_member_path.py`'s `test_add_still_takes_a_name_that_contains_a_dot` asserted `.github` was admitted and its remedy string offered `.github`: both are corrected, `a.b` stays. TESTS, `tests/test_family_member_name.py`, members from local bare remotes (a bare copy of the fixture's spare member under each name): `add --member` over the five spellings of the issue plus `-org/Zed` and `.github`, each `member-malformed`, exit 2, empty stdout, no `gh` call, no throwaway clone, and the holder byte for byte (HEAD, `ls-files -s -z`, `status --porcelain=v1 -z`, `family.yaml`, the rest of the snapshot); controls that a name with an inner `.`, `_` or `-` (and an owner with an inner `.`) still adds in ONE commit and the holder's `validate-family.py` passes; the pinned-equality test and the corpus. MUTANT: the old `MEMBER_RE` restored makes the seven refusal cases and both pin tests fail (9 of 13; the four controls pass), the refusals with exit 0 and a committed row. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Sonnet 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 7 hours and 41 minutes by commenting |
Reviewer's Guide
Sequence diagram for refusing malformed members before side effectssequenceDiagram
participant User
participant Family as family.py
participant Member as Member repository
participant Git as Holder git state
User->>Family: add --member owner/project
Family->>Family: _checked_member(raw)
alt owner/project begins with . _ or -
Family-->>User: Refusal member-malformed
Note over Family,Git: No member read, clone, gh call, or commit
else valid leading characters
Family->>Member: Read member metadata
Family->>Git: Mount, pin, and commit row
Git-->>User: Successful add
end
Flow diagram for member name validation before readingflowchart TD
A["family.py add receives --member"] --> B["_checked_member parses owner/project"]
B --> C{"owner or project is . or ..?"}
C -->|Yes| D["Refuse member-malformed with directory-reference diagnostic"]
C -->|No| E{"MEMBER_RE matches leading letter or digit on both names?"}
E -->|No| F["Refuse member-malformed"]
E -->|Yes| G["checked_value validates the member"]
G --> H["Read and mount member"]
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
brettheap
added a commit
that referenced
this pull request
Oct 12, 2026
…n so CI tests the two stacked (#326) #399 (`MEMBER_RE`, `_checked_member`, #366) lands before this PR and also edits `scripts/family.py`. A Windows CI run now takes about 55 minutes, so this branch is stacked on #399 here: one CI cycle tests main, #399 and #400 together, and this PR can land right after #399 without a second one. The merge was clean (no conflict); `add --dry-run` calls `_checked_member` as `add` does, so the new member-name rule is asked under `--dry-run` through the same function. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
brettheap
added a commit
that referenced
this pull request
Oct 12, 2026
…add-dry-run (#326) Main took #396 (`tests/test_agents_md_codes.py`) after #399's last merge of it, so it is merged here as well: the branch now holds main, #399 and #400 and CI tests all three. The merge was clean (no conflict); the one file it adds is the new test that holds AGENTS.md's code-shaped names to the shipped tools. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
brettheap
added a commit
that referenced
this pull request
Oct 12, 2026
…icate-id` and to #399's `member-malformed` names, so a dry run that skipped either check no longer previews a row the real `add` refuses, a copy of a pinned project under a new name or a name beginning with `.`, `_` or `-` included (#326) Lane 3's #381 landed as f0a8bf6 before this PR: `add` refuses a member whose `project.yaml` records an `id` a row already pins, as `member-duplicate-id`, before it mounts anything. The review of 8bcc57c said whichever of the two landed second must give that refusal a row in `ADD_REFUSALS`, and that is this PR. #399 (`MEMBER_RE`, #366) also lands first and is merged here, so CI tests main, #399 and #400 together; both merges were clean. No behaviour changes. `cmd_add` asks `_checked_member_id` of the probe's reading just before the dry-run return, so the dry run reaches the check through the same function as the real run, with the same id, and `member-duplicate-id` comes out of `--dry-run` with the same stdout, the same stderr and exit 2. Only the `_add_dry_run` docstring, which listed the holder's refusals and the member's, now names it. - `ADD_REFUSALS` gains `duplicate-id`, last, in `add`'s own order: the member is `IRRS2`, a bare copy of the pinned `IRRS` made beside the holder from the remote its `.gitmodules` names and never in the module's shared remotes, as f0a8bf6's own tests build the renamed repository. A set-up may now return the directory of remotes the add reads from; the others return None. - It gains five `malformed-*` rows for #399's rule, after `malformed-dot`: a project beginning with `_`, `-` and `.`, and an owner beginning with `_` and `.`. `<org>/..` and a bare name, the table's old two, are refused by clauses that predate the rule, so none of them covered it. An owner beginning with `-` cannot be said as `--member <owner>/<project>` (argparse reads it as an option): it stays with the real run's test in `test_family_member_name.py`. Mutants, each run on the parametrized refusal test over the `malformed` and `duplicate-id` rows: - the dry run skips `_checked_member_id`: `[duplicate-id]` fails (the dry run exits 0 and prints the row for `IRRS2`; the real run exits 2), the other seven pass; - the dry run takes `--member` without `_checked_member`: the seven `malformed*` rows fail, `[duplicate-id]` passes; - `MEMBER_RE` loosened to the pre-#399 class: exactly the five new rows fail. Targeted set (Linux, git 2.43.0, CI's git-filter-repo pin): `test_family_dry_run.py`, `test_family_add_refusals.py`, `test_family_add_member_id.py`, `test_family_member_name.py`, `test_family_member_path.py`, `test_family.py`, `test_cli_reference.py` and `test_agents_md_codes.py`: 352 passed. Not changed: a linked `project.yaml` whose target records an id a row pins still gets exit 0 from the dry run, and the real run refuses it after the mount and unwinds, inside the module docstring's stated limit. 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 12, 2026
…-bump-remove-rollback, so this PR is tested on top of #400, which lands first and conflicts with it (#365, #360) #400 (`add --dry-run`, #326) carries #399 and main 3ef059c, and lands before this PR. Both rewrote the end of the module docstring's "WHAT ONLY THE WRITE ITSELF CAN ANSWER IS NOT ASKED" paragraph, and both changed `tests/test_family_dry_run.py`'s import list from `test_family_add_refusals`. Resolved as review 365-5d56e75 gives it: - `scripts/family.py`: #400's `.gitmodules` sentence, then this PR's held-`index.lock` sentence, then #400's symbolic-link `project.yaml` sentence, and after a blank line this PR's "A REFUSED `bump` OR `remove` PUTS THE HOLDER BACK" paragraph, unchanged; - `tests/test_family_dry_run.py`: the union of the two import lists. main is not merged separately: #400's head carries it. 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 12, 2026
…mounts, reads the member from throwaway clones outside the holder for the row's id and digest, and prints the row, gitlink, `.gitmodules` section and trailers without mounting, writing or committing anything, and `add` takes `--trailer` as `bump` does, so every subcommand has the dry run the module's `EXIT CODES` line promises (#326) (#400) The second and last part of #326. Part 1 (#345, 3ae0109) gave `bump` and `remove` a `--dry-run`, and left `add` out until the changes to `cmd_add` then in flight had landed. Until now the only way to see what `add` would pin was to run it: clone the member, mount it, rewrite `family.yaml` and commit. `add --dry-run` leaves `cmd_add` after the probe and before `MountRollback` is taken or `git submodule add` runs. So every refusal `add` makes before its first write is the real run's own code, in its order, with the same code, the same words and exit 2. That covers the holder's refusals and the member's, which are asked of the probe's throwaway clone. Lane 3's #381 (f0a8bf6) added `member-duplicate-id` right after the probe, and `cmd_add` asks it before the dry-run return, through the same function, so `--dry-run` refuses it too: real and dry run give byte-identical output, exit 2, and a table row pins it. The same holds for #399's `member-malformed` names beginning with `.`, `_` or `-`. The dry run reads the member a second time, as the real run does. The real run's second reading is the mount, whose reading the row records. The dry run's is a second bare clone, made as the probe makes its own: in a temporary directory outside the holder, deleted whatever the answer. It is read at the commit the probe resolved. The `id:` comes from `_member_id_at`, through the function the checkout is read with. The digest comes from `tree_digest`, which reads objects and never a checkout, so it is the digest the real run computes, never a guess. A `project.yaml` that is a symbolic link is left to the real run's mount, which reads through it; the dry run says so and guesses no id. It prints, in `bump --dry-run`'s layout, and exits 0: - `commit none -> <sha>` and the digest; - the gitlink; - the `.gitmodules` section `git submodule add` writes: name, path, url, and whether the file is new; - the registration in `.git/config`, written or with its url rewritten; - the directory and object store the mount makes; - the row as `family.yaml` would carry it; - the trailers. Without `--at`, a note says the real run pins the tip it finds when it mounts, and gives the `--at` that pins the commit shown. The dry run never runs `git submodule add`. It never writes `.gitmodules`, `family.yaml`, the index, the work tree, `.git/config` or `.git/modules`. `add` takes `--trailer`, `bump`'s repeatable flag. Ruling 4 asks `--trailer` with `--dry-run` to print the trailers it would write, and `add` had none. The real run carries the lines into its one commit through `_commit_one`. Without the flag, the commit is the one `add` has always written. Git's own refusal at the mount is named, not made. With no `.gitmodules` in the work tree and one in the index or HEAD, `git submodule add` refuses before it writes anything, and the real run exits 2 there with the holder unchanged. The dry run says so in a note, with how to put the file back, and exits 0. That is how part 1 treats the checkout's own refusal. The module docstring's `EXIT CODES` line now says every subcommand takes `--dry-run`. `add`'s help names the dry run and says it reads the member. `docs/cli.md` is regenerated. AGENTS.md is not changed (line cap); its family section naming the dry runs and `add --trailer` is a follow-up. The tests are appended to tests/test_family_dry_run.py; part 1's tests are untouched. - A real add previewed, for a holder with members and for a family's first member (absent `.gitmodules` stays absent). These stay byte-identical: HEAD, the index, `git status`, `family.yaml`, `.gitmodules`, `.git/config`, the `.git/modules` listing, the work tree and the holder's objects. No `gh` call and no temporary clone left. The real add then writes exactly the printed row, digest, gitlink, section, registration and directory, and the holder validates. - 19 refusals before the first write, each the same stdout, stderr and exit 2 from the real run and the dry run, with the holder unchanged: the holder's, the member's, `member-duplicate-id` (a copy of a pinned project under a new name) and five `member-malformed` names beginning with `.`, `_` or `-`. - `--trailer` printed, and carried by the real commit. - `--at` previewed and pinned. - A registration the mount rewrites. - The `.gitmodules` note, for an unstaged and a staged deletion. - A linked manifest (POSIX only). - In process: both clones are outside the holder, under the run's TEMP. The holder's `git status`, asked while the dry run's clone exists, is unchanged. No git command in the holder writes, and no `git submodule` runs. - A member whose `project.yaml` records `id: routing-core` under the name `IRDI`: the dry run prints that id, read from the member and not made from its name, and the real add writes exactly the printed row. - The dry run's own clone failing (the member's remote moved away between the two readings): exit 2, its clone removed, the holder unchanged. And a holder that is a linked worktree: the object store printed is the one the real add makes. Mutants, each killed: - the dry-run return moved after `git submodule add`: `index changed`; - the temporary clone made inside the holder: the status asked while the clone exists; - the temporary directory not removed: the leftover check; - an id made from the name (`project.lower()`): the id test; - the dry run's own clone leaked on its failure path: the failing-clone test; - the store assumed to be `.git/modules/<path>`: the linked-worktree test. - the dry run skipping `_checked_member_id`: the `duplicate-id` row; - the dry run skipping `_checked_member`, or `MEMBER_RE` loosened to its pre-#399 class: the `malformed*` rows. Closes #326 Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap
pushed a commit
that referenced
this pull request
Oct 12, 2026
… into fix/adopt-record-submodule-placement Brings in f7f4b14 (#366), cb7063f (#390), 5678f68 (#357) and 53bee5b (#289) on top of 3ef059c, which 563ea16 had merged. Merged by sha, no rebase. One conflict, kept on both sides: - tests/test_manifest_validator.py, the import block. cb7063f adds `import os`, `import subprocess` and `import sys`; this branch had already added `import os` and `import sys`, so only `subprocess` was in conflict. The merge keeps all three, once each, as cb7063f's message says it would, with this branch's `from conftest import REPO, git, run_script` and `from repo_shape import parse_yaml, path_spelling_fault` after them. `git merge-tree --write-tree` of the two parents gives the same tree in every file but that one. Checked: - Every top-level definition of the eleven modules either side changed is the changing side's text, byte for byte. The only ones both sides changed are that import block and shape-doctor.py's module docstring, where the merged docstring differs from main's by exactly this branch's two hunks, and from this branch's by exactly 5678f68's one. - No name is bound twice by the merge. scripts/shape_materialize.py imports `YamlError` and `parse_yaml` twice, but main 3ef059c does that too. - Each module's names are the base's plus what each side added. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap
pushed a commit
that referenced
this pull request
Oct 12, 2026
…plan-manifest-invalid` joins `_entry_findings` after #255's values, and this branch's `plan-root-symlink` stays after the source's own submodules origin/main moved 92e29d9 -> dd8d0ea (twelve commits), merged by sha: - f0a8bf6 (#330 via #381), scripts/family.py; - 87d2eb7 (#333 via #379), update-shape.py; - cd1fc79 (#327 via #375), shape-doctor.py; - 45e01f4 (#348 via #396), tests/test_agents_md_codes.py: every code-shaped name AGENTS.md spells is a code a tool prints; - 3ef059c (#258 and #261 via #314), adopt-project.py, scaffold-project.py and scripts/shape_materialize.py: the project's YAML reader and its `validate-manifest.py` are asked about the manifest's values before anything exists; `check` reports `plan-manifest-invalid` through `_manifest_findings` in `_entry_findings`; `_repository_urls` refuses `adopt-name-too-long`; - f7f4b14 (#366 via #399), scripts/family.py; - cb7063f (#301 and #280 via #390), scripts/repo_shape.py and the template's validate-manifest.py: a bracket or brace in a plain flow scalar is refused; - 5678f68 (#278, #297 and #298 via #357), shape-doctor.py; - 53bee5b (#289 via #352), scripts/bump-leg.py; - f5cb616 (#369 via #398), scripts/shape_materialize.py: the docstrings of `force_stage` and `stage_written`; - a92e60b (#326 via #400), scripts/family.py; - dd8d0ea (#292 via #353), adopt-project.py: `_elector` keeps a `user.name` byte that is not UTF-8, `_remote_repository` reads an origin URL that is not UTF-8 as none, and `adopt-no-org` is asked before `checked_value`. scripts/shape_materialize.py, scripts/repo_shape.py and every other file merge with no conflict. adopt-project.py conflicts in two hunks of `_entry_findings`, each resolved by keeping both sides: 1. The docstring. Main's order, with #261's manifest findings after #255's values and before #190's relative urls; this branch's symlinks stay after the source's own submodules. 2. The return. Main's `_manifest_findings(plan)` call, and the findings in main's order: each entry's path (#196), coverage, the legs and what the root keeps where the shape writes, the source's own submodules (#166), this branch's `_root_symlink_findings`, #255's values, #261's manifest findings, and #190's relative urls last. The remediation and the NOTEs are main's. How each of main's commits that reach this branch's code meets it: - 3ef059c: `_manifest_findings` asks the validator the template ships about the values `execute` writes; it reads no symlink and no path the shape writes. Its materializer half adds the validator's helpers, `refuse_unstorable_names` and `MANIFEST_VALIDATOR`, and holds `election_date` to the validator's `DATE_RE`; it leaves `place`, `_materialize`, `copy_tree`, `template_files` and `_refuse_unwritable` as they were. In `execute`, its `adopt-name-too-long` (in `_repository_urls`) comes before #255's clone-URL refusal, `_refuse_what_check_finds` and this branch's `core.symlinks` probe, all before any leg. Its new paragraph of the module docstring follows the one 6b53a6d corrected; both merge with no conflict. - f5cb616: docstrings only. - dd8d0ea: `_remote_repository`, `_checked_plan_inputs`' organisation and `_elector`, none of them this branch's. - cb7063f: the plans `check` and `execute` read go through this reader. The plans `plan` writes for this branch's tests hold their entries as block mappings, each path `double_quoted` where it needs to be, and the rehearsal below read every one of them. Checked on the merged tree: - AST (fin183g/names.py, base 92e29d9): no top-level name is bound twice in adopt-project.py, scaffold-project.py or scripts/repo_shape.py, and none is loaded but bound nowhere. scripts/shape_materialize.py binds `YamlError` and `parse_yaml` twice, as main dd8d0ea does (1f90a0f's import and 3ef059c's, the same objects); the merge adds none. - Both sides intact (fin183g/sides_intact.py): the only lines that differ from either side's own change are those of the two hunks. - A rehearsal of this merge (tree d21f8b4, the same as this one) passed tests/test_adopt_root_symlinks.py with main's test_adopt_{manifest_values,git_reads,relative_submodule_url}.py, test_scaffold_manifest_values.py, test_agents_md_codes.py and test_yaml_flow_brackets.py: `440 passed`. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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)
Claim: #366 (comment)
What
family.py addrefuses a--memberwhose owner or project does not begin with a letter or a digit, asmember-malformed, before the member is read.MEMBER_REtookQUALIFIED_RE's shape (the validator's, invalidate-family.pyandvalidate-manifest.py); until now it shared the character class but not the first-character rule, sotestorg/_Zed,testorg/.Zed,testorg/-Zed,_org/Zedand.org/Zedwere mounted, pinned and committed with exit 0, and the holder's ownvalidate-family.pythen failed the commit asmember-repository.The rulings (the issue's three decisions):
MEMBER_REtakesQUALIFIED_RE's shape._checked_member's./..clause is KEPT for its more specific sentence and read FIRST, so it is still reachable (only on a name with a/; a bare.is still the general sentence, which asks for the owner). Its remedy no longer offers.githubas the example of a name with a dot, since.githubis refused now; it saysZe.d.family.pyspells the pattern a third time, andtests/test_family_member_name.pyREADS every spelling (MEMBER_REand every tracked module'sQUALIFIED_REoutsidetests/, parsed, never run) and fails when they differ. The constant stays out ofrepo_shape.py, a copied file that would reach an existing holder only throughupdate-shape.py.The general
member-malformedsentence now states the rule, and its remedy says why (a commit that wrote such a name would fail the gate the holder ships).Behaviour that moves beside the five
--member=-org/Zed(the one way a leading-reaches the owner) passed the old pattern and was refused later bychecked_valueasunsafe-value; it ismember-malformednow. Exit 2, nothing read, either way.testorg/.githubandtestorg/...are refused (the issue named them; the validator refused both already).bumpandremovetake the git pathspec from the manifest row'spath:with no check and no--literal-pathspecs, so a hand-editedpath: members/*makesremove --member Atlasexit 0 with ONE commit that unmounts every member while dropping only Atlas's row, andpath: .makes itgit rm -r -fthe holder's own files and die on aFileNotFoundError, thoughvalidate-family.pyalready has a rule,member-path, for whatpath:must equal #299 (PR family.py:bumpandremoverefusemember-pathwhen a row'spath:is notmembers/<project>or itsproject:is not a directory name, and hand git every path literally, so a hand-editedpath: members/*orproject: .no longer makesremoveunmount every member in one commit while dropping one row (#299) #315) deliberately left alone: its comment keptMEMBER_RE's class loose 'for.github', and its writer named this tightening as the follow-up.testorg/.github, and an owner of.github, are refused now; the validator refused both already, so no holder could keep such a row.tests/test_family_member_path.py:test_add_still_takes_a_name_that_contains_a_dotasserted.githubwas admitted, and the./..test's remedy string offered.github. Both are corrected;a.bstays admitted.Tests
tests/test_family_member_name.py, members from local bare remotes (a bare copy of the family fixture's spare member under each name, so a refusal is for the NAME and never for a missing remote):add --memberover the issue's five spellings plus-org/Zedand.github:member-malformed, exit 2, empty stdout, noghcall, no throwaway clone, and the holder byte for byte (HEAD,ls-files -s -z,status --porcelain=v1 -z,family.yaml, the rest of the snapshot)..,_or-(and an owner with an inner.) still adds in ONE commit, the row names the argument verbatim, and the holder'svalidate-family.pypasses.Mutant: the old
MEMBER_RErestored fails the seven refusal cases (six exit 0 with a committed row, and-org/...isunsafe-value, exit 2) and both pin tests, 9 of 13; the four controls pass.Linux,
/usr/bin/python3 -m pytest tests/test_family_member_name.py tests/test_family.py tests/test_family_add_refusals.py tests/test_family_member_path.py -q: 220 passed. CI's three OSes are the gate.Closes #366
🤖 Generated with Claude Code
Summary by Sourcery
Align
family.py addmember-name validation with the holder validator so malformed repository names are refused before any member access or commit.Bug Fixes:
Enhancements:
.and..path segments while updating their guidance for the tightened naming rule.Tests: