Repository navigation
adopt-project.py: plan's table pads each name by the columns a terminal gives it, not the code points it holds, so a wide or combining name's leg column lines up under leg with every other row's (#1034) - #1178
Conversation
…inal gives it, not the code points it holds, so a wide or combining name's `leg` column lines up under `leg` with every other row's (#1034) `_print_entries` padded the name it prints with `:<34`, which counts code points. On a UTF-8 stdout a name of two wide ideographs plus `.md` takes 7 columns and was padded as 5, so its row's `leg` began at column 37, 2 right of the heading's 35; `e` plus a combining acute plus `.md` takes 4 columns and was padded as 5, so its `leg` began at 34. The name is now padded by `_padded`, which measures it with `_columns`: two columns for a character `unicodedata.east_asian_width` calls wide or fullwidth, none for a non-spacing mark, an enclosing mark or a format character (general category Mn, Me or Cf), and one for any other. The mark is asked first, so the wide voiced sound mark U+3099 takes none. The text measured is the one printed, after `_shown` and `_printed`, so an ASCII stdout's escapes are still counted one column each (#900). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reviewer's GuideThe PR makes Sequence diagram for terminal-aware plan table paddingsequenceDiagram
participant Plan
participant EntryPath
participant Shown as _shown
participant Printed as _printed
participant Padded as _padded
participant Columns as _columns
participant Terminal
Plan->>EntryPath: read entry.path
Plan->>Shown: _shown(entry.path)
Shown-->>Plan: displayed text
Plan->>Printed: _printed(displayed text)
Printed-->>Plan: text actually printed
Plan->>Padded: _padded(printed text, 34)
Padded->>Columns: _columns(text)
Columns->>Terminal: category and east_asian_width
Terminal-->>Columns: 0, 1, or 2 columns
Columns-->>Padded: display width
Padded-->>Plan: text plus column-based spaces
Plan-->>Terminal: print aligned entry row
File-Level Changes
Assessment against linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
(lane openRepoShape-3, 2026-10-22T21:25:19Z) Independent review of #1178 at Verdict: NOT READY, on the body. The code is sound. CI MUST
SHOULD
NIT
Verified:
Follow-ups, to file on Brett Heap's word:
Lane: openreposhape-3 (openRepoShape-3) |
… consonant no column, as glibc's `wcwidth` does, so a syllable spelled in its jamo has its `leg` under `leg`, and the rows hold a name wider than 34 columns and a spacing mark (#1034) The fix round of RV1178's review of fa56168. S1: `_columns` counted the medial vowels and final consonants U+1160 to U+11FF and U+D7B0 to U+D7FF one column each, as their category is Lo and their East Asian width N. A terminal draws them inside the syllable, and glibc's `wcwidth` gives them none, so `plan` put the `leg` of U+1112 U+1161 U+11AB `.md` at column 33, where main's `:<34` put it at 34. The two ranges are now `MEDIAL_AND_FINAL_JAMO` and `MEDIAL_AND_FINAL_JAMO_EXTENDED_B`, in `NOT_UTF8_BYTES`'s idiom, and `_columns` counts a character of either as none. The name takes 5 columns, and its `leg` is at 35: a `_columns` row for it, one for a syllable in the jamo of Hangul Jamo Extended-B, and the name in `plan`'s row. S2: no row held `_padded` on a name wider than its columns. Ten of U+540D U+524D and `.md` take 43 columns, and `_padded(name, 34)` is the name as it is. NITs: a spacing-mark row, U+0915 U+093F `.md`, 5 columns; and `plan`'s row asserts that `e` U+0301 `.md` is among its rows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
(lane openRepoShape-3, 2026-10-22T23:55:40Z) Delta review of #1178 at Every finding from the first round is fixed at this head:
What the commit adds:
Checks:
Before landing, read the body's ending back once more, in case a bot appends a block again. Lane: openreposhape-3 (openRepoShape-3) |



What changed
A follow-up outside v1.2.0 (Brett Heap, 2026-10-21T10:32:04Z). It lands only after the v1.2.0 tag, on a later merge word.
plan's table padded each name with:<34, which counts code points, not the columns a terminal gives them. On a UTF-8 stdout the name U+540D U+524D.md(two wide ideographs) takes 7 columns and was padded as 5, so itslegbegan at column 37, 2 right of the heading's 35. The nameeU+0301.md(a combining acute) takes 4 columns and was padded as 5, so itslegbegan at 34. Names that stdout escapes have lined up since #900's PR (#1030).adopt-project.py:_columns(text)counts the columns a terminal gives a text: none for a character whose general category is inZERO_COLUMN_CATEGORIES(Mn, Me and Cf: a non-spacing mark, an enclosing mark, a format character) or that is a Hangul medial vowel or final consonant (MEDIAL_AND_FINAL_JAMO, U+1160 to U+11FF, andMEDIAL_AND_FINAL_JAMO_EXTENDED_B, U+D7B0 to U+D7FF), two for oneunicodedata.east_asian_widthcalls wide or fullwidth (TWO_COLUMN_WIDTHS, W and F), and one for any other._padded(text, columns)pads a text to that many columns, asf"{text:<{columns}}"pads it to that many code points. A wider text is left as it is, as there._print_entriespads_printed(_shown(entry.path))with_padded(..., 34)in place of:<34. The text measured is the one printed, after_shownand_printed, so an ASCII stdout's escapes still count one column each (adopt-project.py: a kept or dropped name reaches the split commit and the pull request body unspelled, and_spelled,_shownandplan's report print text that reads as another name, or raw #900).tests/test_adopt_spelled_names.py:test_a_name_takes_the_columns_a_terminal_gives_it: 14 names whose columns are counted by hand: ASCII, wide, fullwidth, halfwidth, ambiguous, a combining acute, a Thai mark of combining class 0, a Devanagari spacing mark, the wide voiced sound mark, a Hangul syllable spelled in its three jamo, one spelled with jamo of Hangul Jamo Extended-B, an enclosing mark, a zero-width space, and a name as an ASCII stdout escapes it. glibc'swcswidthgives each new name the count the row holds.test_a_name_wider_than_its_column_is_padded_by_no_space: ten of U+540D U+524D and.md, 43 columns, is_paddedto 34 as it is.test_the_table_pads_a_wide_and_a_combining_name_by_their_columns:_print_entrieson a UTF-8 stdout over five names. Every row'slegbegins at column 35, underleg.test_plan_on_a_utf_8_stdout_keeps_each_column_under_its_heading: the issue's table.planruns end to end with a UTF-8 stdout over a source holding the two wide names, the combining one and U+1112 U+1161 U+11AB.md. Each of the four is asserted among the rows, and every row'slegat 35.Fix round
RV1178's independent review of
fa56168a(issuecomment-6351653129). This round is one new commit,c4beba19, on top; nothing was amended or force-pushed.fa56168acounted each one column, where glibc'swcwidthcounts none.planput thelegof U+1112 U+1161 U+11AB.mdat column 33, one worse than main's 34. The two ranges are now named sets in the idiom ofNOT_UTF8_BYTES,MEDIAL_AND_FINAL_JAMOandMEDIAL_AND_FINAL_JAMO_EXTENDED_B, with a comment on why._columnscounts a character of either as none, as it counts Mn, Me and Cf. The name takes 5 columns and itslegis at 35 (Measured). Theconjoining-jamoandconjoining-jamo-extended-brows and theplanrow hold it. M12 drops both sets, M16 the first and M17 the second: each is KILLED. The out-of-scope bullet on the jamo is gone.test_a_name_wider_than_its_column_is_padded_by_no_spaceasserts_padded(n, 34) == nfor the 43-column name. M13 (max(1, ...)) and M14 (abs(...)) are KILLED.spacing-markrow: U+0915 U+093F.mdtakes 5 columns. M15 counts Mc as none and is KILLED.planrow asserts thateU+0301.mdis among the rows too, beside the two wide names and the jamo name.checksentence. Corrected under Left out of scope:check's names table is ASCII becausePlan.names()runs each name throughchecked_value(SAFE_ARG_RE), not because of GitHub's naming rule.adopt-project.py, adopt-project.py: the--planpath incheckandexecute, and the--local-remote-dirpath inexecute'sbare, leg andsplitlines, are named with_shown, as #1154 names--outand--work-dir, so a bidi control, an escape, a line feed or a byte that is not UTF-8 in either no longer reaches the terminal as it was typed (#1152) #1177, adopt-project.py:checkandexecuterun withGIT_NO_LAZY_FETCH=1and refuse a partial-clone source that lacks an object its branches, tags or HEAD reach assource-partial-clone, so neither fetches into the source any more (#1145) #1180 and adopt-project.py: a--source, or a plan'ssource.local_path, with a part of its repository git reads at mode 000 (the.git, itsHEAD,objects,refsorconfig, the git directory agitdir:line names, or a directory above it) is refusedsource-unreadablenaming that part, nevergit-failedand never cloned again (#1151) #1186 among them, and every merge is clean (Merges).Decided where the Direction left it open
unicodedata.combiningcalls combining. The canonical combining class is 0 for marks a terminal still draws over the character before them: the Thai vowel sign U+0E35 (Mn), the enclosing circle U+20DD (Me) and the variation selector U+FE0F (Mn) among them. Mutant M9 askscombining(or Cf) in place of the category, and the Thai-mark and enclosing-mark rows kill it._shownspells every format character (str.isprintableis false for each), so none reaches the table as it is today. The set says what a terminal does, and the zero-width-space row holds_columnsto it (M5).str.isprintableis false for each, so_shownspells them and none reaches_columnsas it is.Left out of scope
legright of its heading.plancannot learn from stdout which way the terminal draws them._columnswith glibc'swcwidth(C.utf8). They differ only on the 64 Yijing hexagram symbols U+4DC0 to U+4DFF (So, East Asian width N) and the 8 code points U+3248 to U+324F (No, width A). Before this round the 232 assigned jamo of the two Hangul ranges differed too. No code point counts differently from glibc where main's one column agrees with it.check's names table ({name:<28}) prints the three namesPlan.names()returns.Plan.names()runs each throughchecked_value, whoseSAFE_ARG_RE(^[A-Za-z0-9._/@+~-]{1,255}$) holds it to ASCII, so each character takes one column. It is unchanged.templates/family-root/scripts/siblings.py:729andscripts/validate-repository-naming.py:1297. They are other files.Measured
A scratch probe runs
planwithPYTHONIOENCODING=utf-8over a source holdingREADME.md,src/app.py, U+540D U+524D.md,eU+0301.mdand U+FF21 U+FF22.md. It measures each row'slegcolumn on its own, withunicodedata(W and F two, Mn, Me and Cf none, any other one). The heading'slegis at column 35. Only the probe's ASCII lines are quoted: each name is given inascii()form.Before, at
961640c6:After, at
fa56168a:Fix round. RV1178's probe, copied into this writer's scratch, runs
planover a source of 13 names, and measures each row'slegwith glibc'swcswidth(C.utf8), not with_columns. The UTF-8 stdout's rows at main, atfa56168aand at this head:At this head every row's
legis at 35 on a UTF-8, an ASCII and a Latin-1 stdout, but the 43-column name's, which no pad can bring back: it is followed by the one space that ends every name.Tests
At
c4beba19, each file runs alone, on Python 3.12.3, astimeout 2400 /usr/bin/python3 -m pytest -q -p no:cacheprovider --basetemp=<scratch>/bt-<name> -o tmp_path_retention_policy=failed <file>. A stubghthat exits 97 is first on PATH, withGH_TOKEN=bogus, an emptyGH_CONFIG_DIR,GIT_SSH_COMMAND=false,GIT_CONFIG_GLOBAL=/dev/nullandEMAILset.tests/test_adopt_spelled_names.pytests/test_shape_doctor.pytests/test_scaffold_leg_path_collisions.pytests/test_repo_hygiene.pytests/test_no_auto_maintenance.pytests/test_path_guard_hygiene.pytests/test_agents_md_codes.pytests/test_cli_reference.pytests/test_update_add_codes_documented.pyThe 2 skips in
tests/test_no_auto_maintenance.pyare its case for a session that began withGIT_CONFIG_GLOBALset, which the stub environment sets.Mutants
M0,
origin/main'sadopt-project.pyunder the rows (tests/test_adopt_spelled_names.py -k "columns or keeps_each_column_under_its_heading or wider_than_its_column"): 17 failed, 1 passed, 26 deselected in 2.15s. The one that passes is #900'stest_plan_under_the_c_locale_keeps_each_column_under_its_heading. Failed:test_a_name_takes_the_columns_a_terminal_gives_it[ascii]test_a_name_takes_the_columns_a_terminal_gives_it[wide]test_a_name_takes_the_columns_a_terminal_gives_it[fullwidth]test_a_name_takes_the_columns_a_terminal_gives_it[halfwidth]test_a_name_takes_the_columns_a_terminal_gives_it[ambiguous]test_a_name_takes_the_columns_a_terminal_gives_it[combining-acute]test_a_name_takes_the_columns_a_terminal_gives_it[thai-mark-of-class-0]test_a_name_takes_the_columns_a_terminal_gives_it[spacing-mark]test_a_name_takes_the_columns_a_terminal_gives_it[wide-voiced-mark]test_a_name_takes_the_columns_a_terminal_gives_it[conjoining-jamo]test_a_name_takes_the_columns_a_terminal_gives_it[conjoining-jamo-extended-b]test_a_name_takes_the_columns_a_terminal_gives_it[enclosing-mark]test_a_name_takes_the_columns_a_terminal_gives_it[zero-width-space]test_a_name_takes_the_columns_a_terminal_gives_it[as-an-ascii-stdout-escapes-it]test_a_name_wider_than_its_column_is_padded_by_no_spacetest_the_table_pads_a_wide_and_a_combining_name_by_their_columnstest_plan_on_a_utf_8_stdout_keeps_each_column_under_its_headingM1 to M17 each break one behaviour of
adopt-project.py, run the whole oftests/test_adopt_spelled_names.py, then restore the file from a copy saved before the first and check it withcmp. M1 to M11 are the first round's, their patterns moved to the new_columns; M12 to M17 are the fix round's.:<34back: the name padded by its code pointstest_the_table_pads_a_wide_and_a_combining_name_by_their_columns,test_plan_on_a_utf_8_stdout_keeps_each_column_under_its_heading_printed(_padded(_shown(...), 34)): measured before stdout's escapestest_the_table_pads_a_name_as_an_ascii_stdout_prints_it,test_plan_under_the_c_locale_keeps_each_column_under_its_headingZERO_COLUMN_CATEGORIEStest_a_name_takes_the_columns_a_terminal_gives_it[combining-acute],test_a_name_takes_the_columns_a_terminal_gives_it[thai-mark-of-class-0],test_a_name_takes_the_columns_a_terminal_gives_it[wide-voiced-mark],test_the_table_pads_a_wide_and_a_combining_name_by_their_columns,test_plan_on_a_utf_8_stdout_keeps_each_column_under_its_headingZERO_COLUMN_CATEGORIEStest_a_name_takes_the_columns_a_terminal_gives_it[enclosing-mark]ZERO_COLUMN_CATEGORIEStest_a_name_takes_the_columns_a_terminal_gives_it[zero-width-space]TWO_COLUMN_WIDTHStest_a_name_takes_the_columns_a_terminal_gives_it[wide],test_a_name_takes_the_columns_a_terminal_gives_it[wide-voiced-mark],test_a_name_takes_the_columns_a_terminal_gives_it[conjoining-jamo],test_a_name_takes_the_columns_a_terminal_gives_it[conjoining-jamo-extended-b],test_a_name_wider_than_its_column_is_padded_by_no_space,test_the_table_pads_a_wide_and_a_combining_name_by_their_columns,test_plan_on_a_utf_8_stdout_keeps_each_column_under_its_headingTWO_COLUMN_WIDTHStest_a_name_takes_the_columns_a_terminal_gives_it[fullwidth],test_the_table_pads_a_wide_and_a_combining_name_by_their_columns,test_plan_on_a_utf_8_stdout_keeps_each_column_under_its_headingtest_a_name_takes_the_columns_a_terminal_gives_it[wide-voiced-mark],test_the_table_pads_a_wide_and_a_combining_name_by_their_columnsunicodedata.combining(or Cf) in place of the categorytest_a_name_takes_the_columns_a_terminal_gives_it[thai-mark-of-class-0],test_a_name_takes_the_columns_a_terminal_gives_it[enclosing-mark]test_a_name_takes_the_columns_a_terminal_gives_it[ambiguous]test_a_name_takes_the_columns_a_terminal_gives_it[halfwidth]test_a_name_takes_the_columns_a_terminal_gives_it[conjoining-jamo],test_a_name_takes_the_columns_a_terminal_gives_it[conjoining-jamo-extended-b],test_plan_on_a_utf_8_stdout_keeps_each_column_under_its_heading_paddedpadsmax(1, ...)spaces (S2)test_a_name_wider_than_its_column_is_padded_by_no_space_paddedpadsabs(...)spaces (S2)test_a_name_wider_than_its_column_is_padded_by_no_spaceZERO_COLUMN_CATEGORIES(the Mc NIT)test_a_name_takes_the_columns_a_terminal_gives_it[spacing-mark]MEDIAL_AND_FINAL_JAMOnot askedtest_a_name_takes_the_columns_a_terminal_gives_it[conjoining-jamo],test_plan_on_a_utf_8_stdout_keeps_each_column_under_its_headingMEDIAL_AND_FINAL_JAMO_EXTENDED_Bnot askedtest_a_name_takes_the_columns_a_terminal_gives_it[conjoining-jamo-extended-b]Merges
git merge-tree --write-treeofc4beba19withorigin/main(961640c6) and with each open PR that touchesadopt-project.py, at the heads GitHub listed live in this round. No open PR touchestests/test_adopt_spelled_names.py.All are clean. #1171 has
_shownspell a default-ignorable character as its code-point escape, U+1160 among them, and adds arepo_shapeimport on a line of its own. The table here pads the text_shownand_printedreturn, whatever it is, so a name #1171 spells is measured as its ASCII escape, one column to each character. No PR addsimport unicodedata, or a_columns,_padded,ZERO_COLUMN_CATEGORIES,TWO_COLUMN_WIDTHS,MEDIAL_AND_FINAL_JAMOorMEDIAL_AND_FINAL_JAMO_EXTENDED_Bof its own, or changes_print_entries.Closes #1034
Lane: openreposhape-3 (openRepoShape-3)
🤖 Generated with Claude Code