Repository navigation
scripts/repo_shape.py: NamingPolicy reads a name with fullmatch at four sites, so a name that ends in a line feed matches no family and validate-repository-naming.py refuses it, and repository-naming.yaml says a pattern is read whole (#1202) - #1266
brettheap wants to merge 1 commit into
Conversation
…t four sites, so a name that ends in a line feed matches no family and `validate-repository-naming.py` refuses it, and `repository-naming.yaml` says a pattern is read whole (#1202) Every pattern of `contracts/repository-naming.yaml` is `^...$`, and `NamingPolicy` read a name against it with `.match` in `descendant_referents` (the family's pattern, then its `split_pattern`) and in `matches` (a role's pattern, then a family's). In Python `$` also matches just before a trailing line feed, so `Atlas\n` was classified as `Atlas`, and `validate-repository-naming.py --project project.yaml` over a leg `repository: "testorg/Atlas\n"`, or over the argument `$'Atlas\n'`, exited 0. The four sites ask `fullmatch` now. Every shipped pattern begins with `^` and ends with `$`, so no name without a line feed changes its answer. A fork's pattern with no `$` read as a prefix of the name under `.match`; the contract's header now says a pattern is read against the whole name, which is the shorter of the two ways the issue offers. `topic_pattern` has no reader outside `tests/test_naming_policy.py`, so a note beside it says a reader must use `fullmatch`, and no row can fail on main for it. `tests/test_naming_policy_line_break.py` reads its names from the data, so a family or a role added to the contract is covered, and holds a fork's policy whose one pattern has no `$` for each site: the two sites of `descendant_referents` stand in series, and the shipped `split_pattern` takes the family pattern's language, so on the shipped data either alone makes the other redundant. Closes #1202 Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Reviewer's GuideImplementationSwitch all four NamingPolicy checks to fullmatch so names must match the entire pattern, and document the same requirement beside topic_pattern.
ConfigurationClarify in the naming-policy contract that patterns are matched against the whole name, including patterns without a trailing dollar anchor.
TestsAdd regression coverage for trailing-line-feed names, whole-name matching of fork patterns, and rejection by the naming validator.
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
|
(lane openRepoShape-5, 2026-11-08T17:11:37Z) Independent review of #1266 (#1202) for lane openRepoShape-5, by rv1266 (opus), at 09697e9: READY There are no MUST or SHOULD findings. The four sites are the only change in behaviour, and only for a name that ends in a line feed. Every row, mutant and gate the body claims reproduces. There are 7 NITs, none blocking. CI's Scope: one commit, What held (measured)
NITs
GatesAll ran through
Every skip in C and D is one of the two template skips in NIT 1. I ran gate E because Merge-trees
The trees for #1249, #1171 and #1236 equal the writer's. Everything is in Lane: openreposhape-5 (openRepoShape-5) |



Goal
#1202: a repository name that ends in a line feed is the name of no family of the naming policy, and
validate-repository-naming.pyrefuses it asnaming-unclassified, by name, over a manifest's legrepository:and over an argument, where main classifiedAtlas\nasAtlasand exited 0.This is a DRAFT on
main(be21751c3, the landing for #1243). It is the follow-up lane 5 filed as #1202 from its independent review of #1166 (#1139), finding 1 (pull/1166 issuecomment-6352173084). #1166 ended the seven module-level patterns of the validators in\Zand read the three ids withfullmatch, and leftNamingPolicy, whose patterns come fromcontracts/repository-naming.yaml, every one^...$, read with.match. The change is one commit,09697e9c5: 3 files, +255 and -4 (git diff --shortstat origin/main...HEAD):scripts/repo_shape.py(+5 -4),contracts/repository-naming.yaml(+8), and the newtests/test_naming_policy_line_break.py(+242, 77 rows).What held on main
Measured on main
be21751c3(production code equal to5926cccab, where the branch was cut), Python 3.12.3, git 2.43.0, a stubghand no network.NamingPolicyread a name with.matchat four sites ofscripts/repo_shape.py:descendant_referents(the family's_re, then its_split_re) andmatches(a role's_re, then a family's). In Python$also matches just before a trailing line feed, and the YAML reader reads a double-quoted"...\n"as the text and a line feed. Somatches,classifyanddescendant_referentsgave a name with a trailing line feed the answers of the name without it (NamingPolicy.load(contracts/repository-naming.yaml), in process):\nclassifyon main\nAtlas,Atlas-spec,Atlas-code('project-leg', 'assembly' / 'spec' / 'code')matches == [],classify is NoneopenFoo('neutral-product', None), alsoproject-leg/assemblymatches == [],classify is NoneThing-Install('install', None)matches == [],classify is Noneuser-wip('workspace', None)matches == [],classify is NoneInkxRouter,MedxScribe('project-leg', 'assembly'), withdescendant_referents('openRouter', 'openxRouter')and('openScribe', 'openxScribe')matches == [],classify is None, anddescendant_referents == ()Atlas-Spec(a capital S) isNonewith and without the line feed, on both.topic_pattern.match('xf-project-atlas\n')is a match on main; no tool readstopic_pattern(see Limits).The gate (
validate-repository-naming.py, run by the root template atMakefile:48andvalidate.yml:310), measured by the new rows run on main's code:--project project.yamlover a scaffolded root whose legrepository:is written"testorg/Atlas\n"(and likewise-specand-code): exit 0, the table naming the leg and its family on two lines (' Atlas', then' project-leg/assembly').Atlas\n,Atlas-spec\n,Atlas-code\n): exit 0, the same split. A name read from stdin is stripped (line.strip()), and is untouched.validate-manifest.pyhas refused such a leg since validate-manifest.py, validate-family.py, family.py and update-shape.py: anid:, a date or arepository:ending in a line feed is refused by name, and a pin'ssource_repository:that is no path orowner/reponame is named as the pin's (#1139) #1166 (manifest-leg-repository,QUALIFIED_REending in\Z), so the root's gate as a whole already failed; the naming step passed. I did not re-run that measurement.What changed
scripts/repo_shape.py: the four sites readfullmatch.NamingPolicyis the only code that matches a name against the data's patterns (grepof the tracked*.py;scaffold-project.py:1535prints the neutral-product pattern's text in a remedy and matches nothing). A one-line note besidetopic_patternsays that a reader must usefullmatch, because$takes a final line feed.contracts/repository-naming.yaml: a paragraph at the end of the header, "A PATTERN IS READ AGAINST THE WHOLE NAME", for a fork's pattern. A pattern with no$was read as a prefix under.matchand is read as the whole name now, so a fork writes the pattern for the entire name. I took this of the two ways the issue offers (the other is to keep.matchand refuse a name that holds a line feed at the entry ofmatches,classifyanddescendant_referents) as the shorter. Every shipped pattern begins with^and ends with$, and the measured sweep below showsfullmatchchanges no answer except a trailing line feed.tests/test_naming_policy_line_break.py(new, 77 rows):families[].examples,roles[].examples, the descendant family'sclaim_without_referent_examples), so a family or a role added to the contract is covered without a line here; adeclared_onlyfamily is asked under its own id;matches == [],classify is Noneanddescendant_referents == ();NamingPolicybuilt from the shipped data with ONE pattern written without$) for each of the four sites;validate-repository-naming.py --projectover a scaffolded root for each of the three roles, a double-quoted baseline, and an argv name for each, with its baseline.AGENTS.md,README.md,docs/and the handbook mirror stay as they are. Before the change I greppedtests/,README.md,docs/(the handbook),AGENTS.mdandcontracts/for text that pins the header's wording or a prefix reading of a policy pattern: nothing reads the contract's comments, and the only patterns a test writes into a policy aretests/test_naming_policy.py:2498's fork form^~[a-z]+$, which has a$, andtests/test_doctor_validator_verdicts.py:324's corrupted(^open(?:, which does not compile.scripts/repo_shape.py. This repository holds no copy of it and no digest of it. A project's copy is pinned by the shape pin's tree digest (sorted-ls-tree-r-v1, computed over the shape's tree at the pinned commit), andupdate-shape.pyreaches it as anupstream-changed scripts/repo_shape.pyrow;contracts/repository-naming.yamlis in the copy lists ofscripts/shape_materialize.py(lines 444 and 652) and reaches a project the same way.fullmatchneeds no import, so no pinned script meets a name it lacks;tests/test_pinned_copy_text.pypasses (hygiene five below).The rows failing on main
tests/test_naming_policy_line_break.pyrun on main's production code (n1202-mut, a detached tree atbe21751c3with the new file copied in; sha256 of the file equal to the branch's): 40 failed, 37 passed (run-new-main-be21751c3.log). The 37 that pass are the baselines (21 family names, 6 role names, 6 descendant names, the double-quoted leg, 3 argv names). The 40 that fail, and why:test_a_name_that_ends_in_a_line_feed_matches_no_familyneutral-product-openxFactory,openXwallet,openChart,openDox,openXdox;install-xFactory-Hermes-Install,Hermes-Install,Keycloak-Install;domain-descendant-MedxChart,LedgerxWallet,codexFactory,MedxGlass,codexDox;project-leg-Atlas,Atlas-spec,Atlas-code,Northwind,Northwind-spec,Northwind-code;family-InkRouter,Northwind;workspace-brett-wip,scott-wip,a1-wipmatches(name + "\n")was the answer for the nametest_a_descendant_name_that_ends_in_a_line_feed_has_no_referentMedxChart,LedgerxWallet,codexFactory,MedxGlass,codexDox,MedxScribedescendant_referentsgaveopenChartandopenxChart, and so ontest_a_forks_descendant_pattern_with_no_dollar_is_read_as_the_whole_namefamily-pattern,split-patternMedxScribetest_a_forks_leg_pattern_with_no_dollar_is_read_as_the_whole_namerole-pattern,family-patternAtlas_xtest_the_naming_gate_refuses_a_leg_repository_that_ends_in_a_line_feedassembly,spec,codetest_the_naming_gate_refuses_an_argv_name_that_ends_in_a_line_feedAtlas,Atlas-spec,Atlas-codeOn the branch all 77 pass. The refusal is, for each: exit 1 and the line
FINDING naming-unclassified: <name>.No name without a line feed changes its answer (measured, not argued):
matches,classifyanddescendant_referentsover 66,688 names (every string of up to 5 characters overA a x X - 1 _, a space and a line feed, and 11 stems with 12 suffixes, with and withoutopen) under 5 declared roles (none,assembly,spec,code,family), on main and on the branch: 333,440 rows, 3,225 differ and every one is a name that ends in a line feed; none that does not; and no name that ends in a line feed matches anything on the branch (probe_inproc.pyandsweep.pyin lane 5'swork/n1202/).tests/test_naming_policy.pypasses unchanged.Mutants
Each applied alone to
scripts/repo_shape.pyon the branch's copy inn1202-mut, run against the new file, and restored with a sha256 check (restored True, and at the end the file is main's bytes again, blob00759f1e6):descendant_referents, the family's_re.fullmatchput back to.matchtest_a_forks_descendant_pattern...[family-pattern]descendant_referents,_split_re.fullmatchput back to.matchtest_a_forks_descendant_pattern...[split-pattern]matches, a role's_re.fullmatchput back to.matchtest_a_forks_leg_pattern...[role-pattern], and the 3 leg and 3 argv gate rowsmatches, a family's_re.fullmatchput back to.matchtest_a_forks_leg_pattern...[family-pattern], and the 3 leg and 3 argv gate rowsNo survivor. M1 and M2 are killed only by the fork rows, and that is the shape of the code, not a gap in the rows: the two sites of
descendant_referentsstand in series, and the shippedsplit_patterntakes the family pattern's language, so on the shipped data either alone makes the other redundant (M5 is what the shipped-data rows kill). A fork's policy whose one pattern has no$is what asks each by itself; it is also the case the contract's new paragraph is written for.Runs
Python 3.12.3 (CI runs 3.14; the rows assert lists, tuples and finding lines, nothing that depends on a
repror anast.dump), through lane 5'srun10.sh(gh stub first on PATH, bogus token, emptyGH_CONFIG_DIR, blank git identity,GIT_SSH_COMMAND=false,GIT_CONFIG_GLOBAL=/dev/null), git-filter-repo 2.47.0 (CI's pin;git filter-repo --versionprintsa40bce548d2c), at head09697e9c5, dirty=0:tests/test_value_pattern_line_break.py(the sibling it imports two helpers from),tests/test_naming_policy.py, and the hygiene five:test_path_guard_hygiene,test_repo_hygiene,test_agents_md_codes,test_cli_reference,test_pinned_copy_texttools/git247/bin/git, built from kernel.org's tarball)NamingPolicyor run the naming gate:test_manifest_validator,test_scaffold_referent_chain_links,test_chain_pin_sources,test_github_name_limits,test_adopt_manifest_values,test_scaffold_manifest_values,test_reader_refusals_named,test_pin_source_env_collisions,test_family_init_valuestests/test_family_init_values.py: 16 at:229, "this template quotes the placeholder", and 3 at:301, "this template quotesid:", as rv1266 printed them with-rs)Before the rebase onto
be21751c3the same files (without the sibling) passed at the then headf552017f1: 720 on 2.43.0 and 720 on 2.47.1. Not run: the full suite, a real Windows host and macOS, andtest_shape_doctor.py(it reads the naming validator through the doctor and no name in it ends in a line feed).Merges
Read again by rv1266 at 2026-11-08T17:10Z: main
46fe6ac19gives tree251537e4, clean, and main12122952bgivesc46cbe55, clean. The new file,test_value_pattern_line_break,test_naming_policyand the hygiene five passed on that predicted merge (751, git 2.43.0), since12122952bchangedtests/test_pinned_copy_text.py.git merge-tree --write-tree <other> 09697e9c5(git 2.43.0) at 2026-11-08T16:29Z, against main and every open PR that touches one of this PR's files (50 open):be21751c3836cb7fec(clean)scripts/repo_shape.py)3146c3ca5f41cf1dca(clean)scripts/repo_shape.py)8162b874e29eee887f(clean)scripts/validate-repository-naming.py, which the new rows run)352dcbc9664990151d(clean)The later lander takes the refresh.
Limits
topic_patternhas no row. Nothing outsidetests/test_naming_policy.py:422reads it at main, and the data'stopic.patternandtopic.id_patternare read by no tool, so a row cannot fail on main for it. The note beside it, and the contract's paragraph, say a reader must usefullmatch.update-shape.py. Until a project takes the newscripts/repo_shape.pyitsvalidate-repository-naming.pyandvalidate-manifest.pyrun the old.match. The root'svalidate-manifest.pyalready refuses a legrepository:that ends in a line feed since validate-manifest.py, validate-family.py, family.py and update-shape.py: anid:, a date or arepository:ending in a line feed is refused by name, and a pin'ssource_repository:that is no path orowner/reponame is named as the pin's (#1139) #1166.naming-unclassifiednames the name as typed, soAtlas\nends the finding's first line and the existing text ("These names match none of the families ...") follows after a blank line; a name holding an ESC prints raw there on main too. Not changed: the issue asks that the name be refused by name, and no wording or code moves.$is read as the whole name now, as the contract's header says. The shipped data has none, and no test writes one.scaffold-project.py,setup-project.py,family.pyandshape_materialize.pytake a name from argv, whichchecked_valuerefuses first;validate-family.pystrips it.Closes #1202
Lane: openreposhape-5 (openRepoShape-5)
🤖 Generated with Claude Code
Summary by Sourcery
Ensure repository naming policy evaluation uses whole-name matching so names ending in a line feed are rejected consistently.
Bug Fixes:
Enhancements:
Documentation:
Tests: