Repository navigation
scaffold-project.py and family.py init: a flag passed empty is refused by name, a passed --id is told what an id may hold once the name passes, and an empty folder this user cannot list is told the permission (#911, #930) - #972
Conversation
…er this user cannot list is told to give this user read, write and search permission on it or choose another --work-dir (--into for an init landed by it), since the run cannot tell whether it holds a tree, and not the held-tree remedy (#930) `target_in_the_way` answers `cannot be read: <strerror>` where listing the folder fails, and `target_remediation` handed every answer but `CANNOT_WRITE_IN` the caller's sentence for a tree that holds something. So an empty `wd/Atlas`, or `init`'s holder, at mode 000, 100, 200 or 300 was refused as `scaffold-target-exists` (`family-target-exists`) and told "choose an empty --work-dir. There is no --force: re-running over a live tree is not a scaffold" (`init`: "if that IS the holder, it is already a family"), a cause neither run can see there. - `shape_materialize.CANNOT_READ` names that answer, and `target_remediation` gives it `_unreadable_remediation`, modelled on `_unwritable_remediation`: nothing was created, this user cannot list that folder so whether it holds a tree cannot be asked, give this user read, write and search permission on it, or choose another `--work-dir` (`--into` where `init` landed by it). - tests/test_dry_run_parity.py: one row per tool and mode, by the dry run and the real run, and `--work-dir` for an `init` landed by it; the remediation picker's rows; `target_in_the_way`'s answer per mode. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d by name and never read as not given, a passed --id is told what an id may hold and is refused after a name the naming policy refuses, and init's derived id remedy rules out an `open<Product>` or `<X>-Install` name (#911) U156, U157 and U160. Each tool read the values it fills in by their truth, so `--id ''`, `--name ''`, `--elected-on ''`, `--reference ''` and `--elected-by ''` (`init`: `--id`, `--name`, `--created-on`, `--created-by`) planned their defaults and exited 0, and with no identity `--elected-by ''` was refused as "no --elected-by", of a flag that was passed. Each is read by `is not None` now, as `adopt-project.py plan` reads its own since #699, and an empty one meets its flag's own rule and is refused there by name: `--id ''` as `scaffold-bad-id` / `family-bad-id`, `--elected-on ''` as `election-date-malformed`, and the free-text values by the project's or holder's own validator. The two `if not given:` label loops (`_refuse_unholdable_manifest`, `_manifest_flags`) test `is None` with them, and the fallbacks, with `scaffold-no-elector` and both `family-no-creator` refusals, answer only where the flag is not given. The id is held to `checked_value`'s alphabet after its own rule, so `--id ''` is not `unsafe-value`. U158 and U159. A passed `--id` is told "pass an --id of lowercase letters, digits and hyphens, starting with a letter or digit", and is refused only once the name is one the naming policy holds: `--project North_Wind --id north_wind` is `naming-unclassified` first, and `--project Foo-Install --id foo_bar` `naming-role-mismatch`, for a form decided by its characters alone (`init`: `_classify_family_name`, `naming-not-a-family` included). A derived id keeps #659's refusal. U162. `FAMILY_NAME_RULE` adds "and not an `open<Product>` or `<X>-Install` name", in `naming-not-a-family`'s words, so `--family open_glass` is no longer sent to `openGlass`; `naming-unclassified`'s remedy says it too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reviewer's GuideThe PR hardens scaffold and family initialization validation by distinguishing omitted flags from explicitly empty values, ordering passed-ID and naming checks to produce actionable refusals, aligning family naming rules with naming-policy exclusions, and giving unreadable existing folders a permission-based remediation; extensive focused and regression tests cover both dry-run and real-run parity. Sequence diagram for initialization validation and refusal orderingsequenceDiagram
participant User
participant Init as scaffold_or_family_init
participant Identity as identity_validation
participant Policy as NamingPolicy
participant IdCheck as passed_id_validation
participant Materialize as target_remediation
User->>Init: invoke with flags
Init->>Identity: read flags with is not None
Identity-->>Init: explicit values or derived defaults
alt passed --id is invalid
Init->>Policy: classify project_or_family_name
Policy-->>Init: naming refusal or accepted name
Init->>IdCheck: validate passed id
IdCheck-->>User: bad-id with id alphabet remediation
else derived id
Init->>IdCheck: validate derived id
IdCheck-->>User: derived-id remediation
end
Init->>Materialize: inspect target folder
alt folder cannot be read
Materialize-->>User: permission remediation and nothing created
else target is writable or readable
Materialize-->>Init: continue or report existing tree
end
Flow diagram for explicit empty flag handlingflowchart TD
A[Flag supplied] --> B{Value is None?}
B -->|Yes| C[Use fallback or derived value]
B -->|No| D[Keep supplied value]
D --> E{Value passes its own rule?}
E -->|No| F[Refuse by flag name and typed value]
E -->|Yes| G[Continue validation]
C --> G
G --> H{Passed --id is invalid?}
H -->|Yes| I[Check project or family naming policy first]
I --> J[Report naming refusal or actionable bad-id remediation]
H -->|No| K[Continue initialization]
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-18T20:24:21Z) RV972 (independent opus review, ruling 8) of #972 at head 0094731: NOT READY, one MUST. Probes (stub gh exit 97, GH_TOKEN=bogus, GIT_SSH_COMMAND=false, uid 1000):
MUST SHOULD NIT Tests at 0094731, one file at a time: empty_values 13, derived_id_refusals 57, github_name_limits 64, github_limits_init_check_org 93, agents_md_codes 47, cli_reference 23, repo_hygiene 81, no_auto_maintenance 60, dry_run_parity 123, family_init_values 106 passed 19 skipped, scaffold_e2e 46, family 112, family_init_asks_every_value 78, family_init_landing 19, family_dry_run 61. Predicted merges with #960 and #969: all passed. Lane 3's ruling: one fix round, by lane 3's writer W972f, takes M1, S1 and S2 and NITs 1 to 4. For S2, the path flags passed empty ( Lane: openreposhape-3 (openRepoShape-3) |
…s refused after a `--project` no assembly root has, `Atlas-spec` and `Atlas-code` among them, and after a `--family` no Windows disk or GitHub can hold, and an empty `--work-dir`, `--local-remote-dir` or `--into` is refused by name, never read as `.` (#911) Fix round 1 of #972, after RV972's review. M1. `_refuse_bad_id` asks `accepts_role(found, "assembly")` of every form, not only of `UNAMBIGUOUS_FORMS`: a name the policy holds as no assembly root with nothing declared is none whatever is declared later, so `--project Atlas-spec --id foo_bar` is refused as `naming-role-mismatch`, which its retype met before, while openGlass, MedxGlass and NorthWind still reach `scaffold-bad-id`. The unused UNAMBIGUOUS_FORMS import goes. S1. `_refuse_bad_family_id` asks `_refuse_an_unholdable_holder_name` and `_refuse_a_holder_name_github_cannot_hold` (the GITHUB_NAME_MAX check, split out of `_refuse_what_github_cannot_hold`) after `_classify_family_name` and before a passed id's refusal: `--family NUL --id foo_bar` is `family-bad-holder-name`, and a 105-character `--family` with a bad id is `family-name-too-long`. S2. The scaffold's `--work-dir` and `--local-remote-dir`, and `init`'s `--work-dir`, `--local-remote-dir` and `--into`, are parsed as text and made `Path`s by `shape_materialize.folder_flag_path`, which refuses an empty one as `work-dir-no-path`, `local-remote-dir-no-path` or `into-no-path`, as `pin_source_path` refuses `--pin-source ''`, before anything is read or made, the dry run alike. A typed `.` and a flag not given mean what they did. NIT 1. `naming-unclassified`'s `--family` remedy says "cannot be declared into a holder". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
(lane openRepoShape-3, 2026-10-19T14:10:43Z) RV972 round 2 (independent opus review, ruling 8) of #972 at 4e44df4: READY. Every round-1 item holds under my own probes (stub gh exit 97, GH_TOKEN=bogus, GIT_SSH_COMMAND=false):
Ruling on the new codes: documenting them is not required before landing. test_agents_md_codes only checks that the codes AGENTS.md names are printed by a tool; test_cli_reference only holds docs/cli.md to --help, which did not change. AGENTS.md (at its 494-line cap), the README and the handbook do not name pin-source-no-path either. Its only documentation is the --pin-source --help text, and so docs/cli.md. Tests at 4e44df4, one file at a time: empty_values 31, derived_id_refusals 60, github_name_limits 64, github_limits_init_check_org 94, agents_md_codes 47, cli_reference 23, repo_hygiene 81, no_auto_maintenance 60, dry_run_parity 123, family_init_values 106 passed 19 skipped, scaffold_e2e 46, family 112, family_init_asks_every_value 78, family_init_landing 19, family_dry_run 61. On the predicted merge with main c7a3ff1: empty_values 31, derived_id_refusals 60, dry_run_parity 123, cli_reference 23, all passed. Mutants, all killed by failed assertions: E3 in reverse (Atlas-spec and Atlas-code rows), SM12 (3 rows), FM7 (13), S1a ([NUL]), S1b (test_init_refuses_a_long_family_before_a_passed_bad_id), S2a (10 empty-flag rows), S2b (5 dot rows), and mine, E10 (empty read as not given: the 10 rows, and the suite's gh guard caught the 4 --local-remote-dir rows going to GitHub). Merges (merge-tree) of 4e44df4 with main c7a3ff1 (and cf66102, a19d69c, ae63d45), #971 155fcd2, #960 dc5277e, #957 d673e09, #955 0606cf9, #946 5d5d06b, #988 203e941 and #995 5f46cd3: all clean. #981 and #964 were clean before they landed. Lane 3's note:
Lane: openreposhape-3 (openRepoShape-3) |
|
(lane openRepoShape-4, 2026-10-19T14:26:00Z) Re-check of #972's S1, S2 and NIT 1 are each fixed as RV972 asked. One SHOULD (a missing test row, below) does not block; it can go in with the follow-up lane 3 files for N6, or as one row here. The PR head was still Findings of the
Probes (stub gh exit 97 first on PATH, GH_TOKEN=bogus, empty GH_CONFIG_DIR, GIT_CONFIG_GLOBAL=/dev/null, GIT_CONFIG_NOSYSTEM=1, GIT_SSH_COMMAND=false, local remotes only, no GIT_ALLOW_PROTOCOL;
Tests at Mutants: 17 made, 14 killed, 3 survived. Survivors: the helper's New findings
Out of scope, not judged: the scaffold part (M1), Lane: openreposhape-4 (openRepoShape-4) |
|
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 4 days and 8 hours by commenting |
… refuses a --project no Windows disk can make a folder of, by init's question of --family, and five folder flags' --help say what an empty value and . mean (#989, part of #999) (#1010) A --project that names a Windows device is now refused by the scaffold. `scaffold-project.py --project NUL`, `Con`, `Nul`, `com1`, `Lpt9`, `COM9`, `Aux` or `Prn` exits 2 as `scaffold-bad-project-name`, under `--dry-run` and in a real run, against GitHub and under `--local-remote-dir`, with no `gh` call and nothing made. Each was taken before, and the three repositories and their folders were made, where `family.py init` refused the same `--family`. `Nullable`, `Console` and `Auxo` still scaffold: a device name is the whole name. One function, `shape_materialize.refuse_an_unholdable_name`, asks the question for both tools, each in its own code, flag and remedy, so the two cannot part again. `init`'s `family-bad-holder-name` keeps its words byte for byte. The scaffold asks it after the naming policy and before a passed id's alphabet, as `init` does, so a name the policy refuses (`NUL.git`, `Nul.`) is refused as the policy refuses it. The five folder flags' --help (the scaffold's `--work-dir` and `--local-remote-dir`; `init`'s `--into`, `--work-dir` and `--local-remote-dir`) each gain a clause: an empty value is refused as `<flag>-no-path`, and `.` means the directory the run stands in. `docs/cli.md` is regenerated. Lane 4's SHOULD-1 on #972 is one row: a local run asks no length of a family before a passed bad id. Follow-up, for adopt's `plan` and `check` and two NITs: #1014. Closes #989 Part of #999 Lane: openreposhape-3 (openRepoShape-3) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>



Closes #911.
Closes #930.
Three commits: one per issue, then fix round 1 after RV972's review. Both tools' dry runs refuse each case below as their real runs do, before anything is made.
Fix round 1
After RV972's review (one MUST, two SHOULDs, four NITs; lane 3's ruling in its comment). One commit,
4e44df40, on top of the first round's two.M1:
Atlas-specandAtlas-codespeak before a passed id_refuse_bad_idnow refuses a passed id that is no id only afteraccepts_role(found, "assembly")holds for--project, whatever its form, as RV972 measured. The first round asked that only ofUNAMBIGUOUS_FORMS, so--project Atlas-spec --id foo_barwas refused asscaffold-bad-id, and the retype--id foo-barthen metnaming-role-mismatch, which no--idmends.project-leg/assembly, which it does with no pin too. SoopenGlass,MedxGlassandNorthWindstill reachscaffold-bad-id. The docstring says this, and the unusedUNAMBIGUOUS_FORMSimport goes.test_a_form_no_assembly_root_has_speaks_before_a_passed_idgainsAtlas-specandAtlas-coderows, each with a bad id and a good one.S1: a name no holder can have speaks before a passed id
_refuse_bad_family_idnow asks, for a passed id that is no id,_classify_family_name, then_refuse_an_unholdable_holder_name, then GitHub's 100-character limit, beforefamily-bad-id. The limit is_refuse_a_holder_name_github_cannot_hold, split out of_refuse_what_github_cannot_hold, which still asks it first, and neither asks it under--local-remote-dir. None of the three reads anything but the name, the policy and whether--local-remote-dirwas given, so none starts a process.--family NUL --id foo_baris nowfamily-bad-holder-name, and a 105-character--familywith--id foo_barisfamily-name-too-long. One row each:test_a_name_no_holder_can_have_speaks_before_a_passed_id[NUL]andtest_init_refuses_a_long_family_before_a_passed_bad_id.S2: a folder flag passed empty is refused by name
The scaffold's
--work-dirand--local-remote-dir, andinit's--work-dir,--local-remote-dirand--into, weretype=Path, andPath('')is.: passed empty, each was read as the directory the run stood in, without a word. From the root of a checkout of openRepoShape,init --into ''planned the holder inside it.They are parsed as text now, as
--pin-sourceis, andshape_materialize.folder_flag_pathmakes each aPath, refusing an empty one by name inpin-source-no-path's words, with three new codes:work-dir-no-path,local-remote-dir-no-pathandinto-no-path. Both tools print the same code for the same flag, as both printpin-source-no-path.The scaffold asks them beside its
--pin-sourcecheck, andinitfirst incmd_init(_folder_flags), before anything reads them or any process starts, so the dry run and the real run refuse alike and make nothing.The remedy names the three ways on: a path,
.for this directory, or no flag and what that means.init --into ''says:A typed
.and a flag not given mean what they did.--helpis unchanged, and neithertests/test_agents_md_codes.py(codesAGENTS.mdnames must be printed) nortests/test_cli_reference.py(docs/cli.mdagainst--help) asks for a new code to be written anywhere, so no document changes; the codes are named here.tests/test_scaffold_init_empty_values.pygains a row for each of the five flags, run dry and for real from an empty folder, asserting the whole refusal and remedy lines and that nothing under the test's folder was made, and two controls: a typed.still plans that folder, and no--work-diror--intostill takes its default.NITs
naming-unclassified's--familyremedy says "cannot be declared into a holder".adopt-project.py planreads only--idand--elected-bybyis not Nonesince adopt-project.py:planrefuses--id ''and--elected-by ''by name, a derived id's refusal says to change--project, or in place to rename the source first, and an org read from a url is named as not given (#658) #699;--elected-onand--referenceare still read by their truth, which is adopt-project.pyplanandcheck: an empty flag or a bare plan value is taken without a word, and the id refusals name what the person did not type #902's.005dd7c0were run again at this head, with the other eleven below.Tests at
4e44df40Each under
timeout 2400, a stubghfirst on PATH that exits 97,GH_TOKEN=bogus, an emptyGH_CONFIG_DIRandGIT_SSH_COMMAND=false, one file at a time:/usr/bin/python3 -m pytest -q -p no:cacheprovider --basetemp=<scratch>/bt-f1-<file> -o tmp_path_retention_policy=failed tests/<file>.py.tests/test_scaffold_init_empty_values.py: 31 passedtests/test_derived_id_refusals.py: 60 passedtests/test_github_name_limits.py: 64 passedtests/test_github_limits_init_check_org.py: 94 passedtests/test_agents_md_codes.py: 47 passedtests/test_cli_reference.py: 23 passedtests/test_repo_hygiene.py: 81 passedtests/test_no_auto_maintenance.py: 60 passedtests/test_dry_run_parity.py: 123 passedtests/test_family_init_values.py: 106 passed, 19 skippedtests/test_scaffold_e2e.py: 46 passedtests/test_family.py: 112 passedtests/test_family_init_asks_every_value.py: 78 passedtests/test_family_init_landing.py: 19 passedtests/test_family_dry_run.py: 61 passedMutants at
4e44df40Each breaks one line, runs the named tests, and restores the file from HEAD, checked by sha256. Each was killed by failed assertions, not a crash.
"Empty" is
tests/test_scaffold_init_empty_values.py, "ids"tests/test_derived_id_refusals.py, "limits"tests/test_github_limits_init_check_org.pyand "parity"tests/test_dry_run_parity.py.UNAMBIGUOUS_FORMSlimit put back on the earlynaming-role-mismatch(it survived the first round)test_a_form_no_assembly_root_has_speaks_before_a_passed_id[Atlas-spec-...]and[Atlas-code-...]_identity:args.id or project.lower()test_a_flag_passed_empty_is_refused_by_name[scaffold--id]naming-role-mismatchtest_a_form_no_assembly_root_has_speaks_before_a_passed_id, all three rows_classify_family_namebefore a passed idtest_a_name_whose_derived_id_is_refused_is_refused_by_the_naming_policy, its 12initrows, andtest_a_name_no_holder_can_have_speaks_before_a_passed_id[openGlass]CANNOT_READbranch intarget_remediationtest_an_empty_working_tree_this_user_cannot_list_is_told_the_permission, all 16 rows, andtest_the_remediation_is_chosen_by_what_stands_in_the_waytest_a_name_no_holder_can_have_speaks_before_a_passed_id[NUL]test_init_refuses_a_long_family_before_a_passed_bad_idfolder_flag_pathnever refuses, so''is.againtest_a_folder_flag_passed_empty_is_refused_by_name, all 10 rowsfolder_flag_pathrefuses a typed.tootest_a_folder_flag_typed_as_a_dot_is_still_this_directory, all 5 rows#911: empty values, the passed id's remedy, and the derived
--familyremedyEmpty values read as not given (U156, U157, U160)
scaffold-project.pyreads--id,--name,--elected-on,--referenceand--elected-bybyis not None(_identity,_elector), andfamily.py initreads--id,--name,--created-onand--created-bythe same way (_checked_family_identity,_template_values,_creator_name).adopt-project.py planhas read its--idand--elected-bythis way since adopt-project.py:planrefuses--id ''and--elected-by ''by name, a derived id's refusal says to change--project, or in place to rename the source first, and an org read from a url is named as not given (#658) #699; it still reads--elected-onand--referenceby their truth, which is adopt-project.pyplanandcheck: an empty flag or a bare plan value is taken without a word, and the id refusals name what the person did not type #902's.--id ''is refused asscaffold-bad-id/family-bad-id:--id '' must match ^[a-z0-9][a-z0-9-]*$;--elected-on ''is refused aselection-date-malformed: --elected-on '' is not a YYYY-MM-DD date;--name '',--reference ''and--elected-by ''are refused asscaffold-manifest-invalid, andinit's--name '',--created-on ''and--created-by ''asfamily-manifest-invalid, each in the validator's own words (--elected-by is '', which the project's own validate-manifest.py refuses: manifest-elected-by: elected_by is empty. ...).if not given:label loops (_refuse_unholdable_manifest,_manifest_flags) now testis None, so a passed''is named as the flag and never as "(not given, so ...)"._elector_namedreads the flag the same way.git config user.namenow answer only where the flag is not given. So doscaffold-no-electorand bothfamily-no-creatorrefusals. With no identity at all,--elected-by ''is no longer told "no --elected-by"._refuse_what_github_cannot_hold, both tools) loses its "empty, so" arm, which a--id ''can no longer reach.A passed
--idis told what an id may hold, after the name (U158, U159)planandcheck: an empty flag or a bare plan value is taken without a word, and the id refusals name what the person did not type #902 to giveadopt-bad-id.initasks_classify_family_namefirst. That check is the whole holder check,naming-not-a-familyincluded, and reads nothing but the policy. Since fix round 1 it also asks_refuse_an_unholdable_holder_nameand, without--local-remote-dir, GitHub's 100-character limit before the id.--projectfirst. It raises the samenaming-unclassifiedthat_check_one_nameraises, now built in one place (_unclassified), and the samenaming-role-mismatchfor a name that is no assembly root's form with nothing declared (_role_mismatch:Foo-Install, and since fix round 1Atlas-specandAtlas-code).family.py init: a derived id's refusal names--idand its remedy leads to a naming refusal #659's refusal, whose remedy is already the naming rule.checked_value's alphabet after its own rule, not before. So--id '',--id=-northand--id 'north wind'are now*-bad-id(they wereunsafe-value). A trailing line break, whichPROJECT_ID_RE.matchlets through, is stillunsafe-value.The derived
--familyremedy andopenGlass(U162)FAMILY_NAME_RULEadds "and not anopen<Product>or<X>-Installname", innaming-not-a-family's words. So--family open_glassis no longer sent toopenGlass.#930: an empty target or holder folder this user cannot list (U129)
shape_materialize.target_remediationnow givestarget_in_the_way'scannot be read: <strerror>answer (CANNOT_READ) its own remedy,_unreadable_remediation, modelled on_unwritable_remediation:--work-dir(--intofor aninitlanded by it), and run this again.Decisions the Directions left open
No new refusal code for an empty value of these nine flags. Each meets its own flag's rule, as adopt's
--idand--elected-bydo since adopt-project.py:planrefuses--id ''and--elected-by ''by name, a derived id's refusal says to change--project, or in place to rename the source first, and an org read from a url is named as not given (#658) #699. The folder flags of fix round 1 have no rule of their own to meet, so they get*-no-pathcodes, afterpin-source-no-path. Neither docs guard asks for any of them to be written down.The name speaks first only for a passed id. A derived id is bad only when the name holds a character no id takes, and scaffold-project.py and
family.py init: a derived id's refusal names--idand its remedy leads to a naming refusal #659's derived refusal already names the naming rule. Its tests still hold.In the scaffold, the early name check asks the policy about
--projectin the assembly role with nothing declared (fix round 1, M1):naming-unclassifiedwhere no pattern matches;naming-role-mismatchwhere the answer is no assembly root's form.Nothing declared turns that no into a yes, so these are the refusals
_check_declared_namesgives later.--pinand--referent-chainare read by_declaration, possibly withgh api, and the tests that hold "no process before these refusals" still pass. The first round limited the mismatch toUNAMBIGUOUS_FORMS, which letAtlas-specthrough to the id.U162's clause goes into
FAMILY_NAME_RULE, not only the derived remedy.naming-unclassified's remedy for--familystates the same rule (scaffold-project.py andfamily.py init: a derived id's refusal names--idand its remedy leads to a naming refusal #659 keeps the two in the same words), and a passed id now meets that refusal first. Its old tail, "the same rule an assembly root's name follows", would no longer be true, since an assembly root may beopenGlass. It now reads "A holder is spelled like an assembly root, but those two forms are unambiguous by construction and cannot be declared into a holder."checked_valuemoved after the id's own rule. This changes three refusals fromunsafe-valueto*-bad-id: a leading hyphen, a space, and''. The bad-id refusal says what an id may hold.scaffold-project.py and
family.py init: an empty target or holder folder this user cannot read is refused with the held-tree remedy ("There is no --force"), which names the wrong cause #930's remedy keys on thecannot be read:answer, whatever the system's reason. In practice that is EACCES, and the run cannot tell a tree from an empty folder in any such case.Out of scope
family.py init: an empty flag value is read as not given, a passed--idis told to be "explicit lowercase" whatever its fault, and the derived--familyremedy leads tonaming-not-a-family#911's State listsscripts/family.py:2353among the label loops. At5017830, and on main, that line is_refuse_an_unusable_commit_identity's loop over GIT_AUTHOR_* and GIT_COMMITTER_*. There, an empty variable is unset, as git andenv_commitread it. It is not a flag label, and the Direction's "two loops" are the scaffold's and_manifest_flags. Left as is.adopt-project.py's half of the id sentence is adopt-project.pyplanandcheck: an empty flag or a bare plan value is taken without a word, and the id refusals name what the person did not type #902's. The sentence used here is offered for it.setup-project.py'scheck_family_nameFINDING keeps its own copy of the--familyrule's words, without the new clause. Not this lane's file.family.py add --local-remote-dir ''is still read as.:addis lane 4's, and outside scaffold-project.py andfamily.py init: an empty flag value is read as not given, a passed--idis told to be "explicit lowercase" whatever its fault, and the derived--familyremedy leads tonaming-not-a-family#911 and the review's S2.setup-project.py's own--intoand--local-remote-dirare not touched.--project NULand--project Con, whichinitrefuses asfamily-bad-holder-name. That predates this PR, and lane 3 files it as a follow-up (the review's N5).Tests
Every run below used:
timeout 2400;ghfirst on PATH that exits 97,GH_TOKEN=bogusand an emptyGH_CONFIG_DIR;/usr/bin/python3 -m pytest -q -p no:cacheprovider --basetemp=<scratch>/bt-<file> -o tmp_path_retention_policy=failed tests/<file>.py, one file at a time.These are the first round's results, at
0094731bunless marked; the four files marked ran at005dd7c0. Fix round 1's, at4e44df40, are in its section above, those four files among them.Changed or added:
tests/test_scaffold_init_empty_values.py: 13 passedtests/test_derived_id_refusals.py: 57 passedtests/test_dry_run_parity.py: 123 passedtests/test_github_name_limits.py: 64 passedtests/test_github_limits_init_check_org.py: 93 passedtests/test_scaffold_e2e.py: 46 passedExisting tests for these files:
tests/test_family_init_asks_every_value.py: 78 passed (at005dd7c0)tests/test_family_init_landing.py: 19 passed (at005dd7c0)tests/test_family_init_values.py: 106 passed, 19 skipped (at005dd7c0)tests/test_family_dry_run.py: 61 passed (at005dd7c0)tests/test_family_refuse_up_front.py: 79 passedtests/test_family_tracking_branch.py: 28 passedtests/test_family_printed_quoting.py: 84 passedtests/test_family.py: 112 passedtests/test_scaffold_free_text_values.py: 113 passedtests/test_scaffold_manifest_values.py: 105 passedtests/test_scaffold_leg_path_collisions.py: 104 passedtests/test_scaffold_leg_paths.py: 27 passedtests/test_scaffold_pin_collisions.py: 33 passedtests/test_scaffold_referent_chain_links.py: 44 passedtests/test_scaffold_pin_and_reuse.py: 75 passedtests/test_scaffold_reuse_seeded_legs.py: 30 passedtests/test_scaffold_ignored_shape_files.py: 15 passedtests/test_scaffold_mount_ignored.py: 42 passedtests/test_non_utf8_git_reads.py: 52 passedtests/test_reference_default.py: 18 passedtests/test_dry_run_asks_remotes.py: 32 passedtests/test_template_quoted_values.py: 67 passedtests/test_clone_url_bytes.py: 30 passedtests/test_setup_family_folder.py: 16 passedtests/test_setup_project_name_edges.py: 97 passedtests/test_siblings_family_name.py: 150 passedtests/test_naming_policy.py: 249 passedtests/test_stdout_encoding.py: 47 passed, 1 skippedtests/test_non_utf8_name_callers.py: 36 passedDocs guards and the doctor:
tests/test_agents_md_codes.py: 47 passedtests/test_cli_reference.py: 23 passedtests/test_update_add_codes_documented.py: 6 passedtests/test_repo_hygiene.py: 81 passedtests/test_no_auto_maintenance.py: 60 passedtests/test_shape_doctor.py(run once, sinceshape_materialize.pychanged): 445 passed, 3 skippedOn the predicted merge with #960 (
git merge-treeof this head andc6700b7f, checked out locally),tests/test_scaffold_init_empty_values.py,tests/test_derived_id_refusals.py,tests/test_family_init_yaml_words.pyandtests/test_dry_run_parity.pygave 200 passed.Mutants (first round, at
0094731b)Each mutant breaks one fixed line, then runs the named test(s). Each was killed by a failed assertion, not a crash, and the tree was restored from HEAD after each. "Empty" is
tests/test_scaffold_init_empty_values.py, "ids" istests/test_derived_id_refusals.py, and "parity" istests/test_dry_run_parity.py._identity:args.id or project.lower()[scaffold--id]args.name or project[scaffold--name]args.elected_on or today[scaffold--elected-on]args.reference or default_reference(...)[scaffold--reference]_elector:if not elected_by:[scaffold--elected-by],test_with_no_identity_...[scaffold]scaffold-no-electorraised outside the fallbacktest_with_no_identity_...[scaffold]_refuse_unholdable_manifest:if not given:[scaffold--elected-by],[scaffold--name]test_a_passed_bad_id_is_told_what_an_id_may_hold[foo_bar-scaffold]naming-unclassifiedbefore a passed idtest_a_name_whose_derived_id_..._naming_policy[_-north_wind-scaffold]checked_valueon the idtest_an_id_the_id_rule_lets_through_still_meets_the_alphabet[scaffold]checked_valuebefore the id's ruletest_an_empty_id_is_a_passed_id_refused_by_name[scaffold],test_a_passed_bad_id_...[-north-scaffold]naming-role-mismatchtest_a_form_no_assembly_root_has_speaks_before_a_passed_idargs.id or family.lower()[family--id]_creator_name:if not created_by:[family--created-by],test_with_no_identity_...[family]args.name or family[family--name]args.created_on or today[family--created-on]_manifest_flags:if not given:[family--created-by],[family--name]test_a_passed_bad_id_...[foo_bar-family]_classify_family_namebefore a passed id...[_-north_wind-family],test_a_name_no_holder_can_have_speaks_before_a_passed_idFAMILY_NAME_RULEwithout the clausetest_init_s_derived_remedy_rules_out_what_naming_not_a_family_refuses,test_a_derived_id_..._that_flag[family]checked_valueon the idtest_an_id_the_id_rule_lets_through_still_meets_the_alphabet[family]CANNOT_READbranch intarget_remediationtest_an_empty_working_tree_this_user_cannot_list_is_told_the_permission[scaffold-000-dry-run]and[family-300-real-run],test_the_remediation_is_chosen_by_what_stands_in_the_way_unreadable_remediationalways names--work-dir...cannot_list_is_told_the_permission[family-100-dry-run]"cannot" in whyinstead of thecannot be read:prefixtest_the_remediation_is_chosen_by_what_stands_in_the_wayEquivalent mutants, not counted. Three
is not Nonereadings are changed for consistency, but no run can reach them with'', because the empty value is refused before they are read:_refuse_what_github_cannot_hold, in both tools;_elector_named;_print_plan's reference note.Merges
git merge-tree --write-treeof this head,4e44df40, with each, after a fetch at 2026-10-19T11:44Z:cf66102fscaffold-project.py,scripts/family.py,scripts/shape_materialize.pyc4dd19dc)l3-915-scaffold-git-promptse40af32dscaffold-project.pyl3-909-chain-pin-sourcesac5b5307scaffold-project.pyfix/906-printed-sentences369f2781scripts/shape_materialize.pyfix/929-init-yaml-wordsb8e62456scripts/family.py,scripts/shape_materialize.pyfix/920-add-gitlink-and-null-members39f84646scripts/family.pyfix/430-g-family-py-unreadable0606cf92scripts/family.pyfix/905-bump-fetch-refspecs5d5d06bbscripts/family.pyNo other open PR touches these files.
init's import offolder_flag_pathjoinsfamily.py's firstshape_materializeimport list: as a line of its own besideshown_path's import it conflicted with the import #955 adds there. Theinitchanges stay in its flag reading (build_parser's three folder flags,_folder_flags,cmd_init's first line), its name and id checks (_refuse_bad_family_id,_refuse_a_holder_name_github_cannot_hold,_classify_family_name's remedy),_template_values,_manifest_flagsand_refuse_a_taken_holder's docstring, clear of lane 4's sites.Lane: openreposhape-3 (openRepoShape-3)
🤖 Generated with Claude Code