Skip to content

Commit 1e2a4ba

Browse files
authored
Read a Tool Row Without a Trailing or Leading Pipe, and Report the License Column (#636)
The 3rd Party Tools check required both outer pipes on every table row, which GitHub's Markdown makes optional, so a row written without either was skipped outright and its link, description and ordering all went unread. A table written that way scored clean rather than unread. spec/project-types.json asserted "The section carries no license column" at verdict letter, and nothing read it. The table header is now read for that column, once per table rather than once per row, and it is the only extra column named, since spec/readme-structure.md forbids that one and no other. table_cells requires a separating pipe rather than a leading one, which is what keeps a thematic break out: `---` alone carries no pipe, where treating it as a one-cell delimiter row would have read the paragraph above it as a table header. Measured against all 22 cataloged READMEs, in both directions: 56 findings become 57, the addition being Blog's License column, no verdict is lost, and the leading-pipe widening moves nothing. Neither outer-pipe form appears in the fleet today, so both fixes close a silent blind spot rather than a live miss. Raised by Copilot on promotion pull request #635.
1 parent 0bb43c5 commit 1e2a4ba

2 files changed

Lines changed: 61 additions & 6 deletions

File tree

‎TODO.md‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,13 +134,13 @@ The spec rework and its audit check shipped. What remains is the per-repo confor
134134
-**Settled** - Three order findings are genuine and each is one move: LanguageTags places Installation after Usage, aiopurpleair places Getting Started after Installation, and PlexCleaner places Questions or Issues immediately after the Table of Contents where the order now puts it ninth.
135135
-**Settled** - Two placement findings are genuine: KiCadLibrary carries a `## TODO` after `## License`, which the "TODO.md" rule already forbids, and HomeAutomation-Config renders the license shield twice, once outside the License section.
136136
-**Settled** - MediaTools carries a `NuGet Pre-Release` shield that renders the same version as its `NuGet Release` shield, and it is dropped on that repo's next visit. The check does not report it, because a shield class is a floor and an extra shield is never a finding.
137-
-**Settled** - Blog is the only repo carrying a `3rd Party Tools` table today, and it needs both fixes the rule now states: drop the License column, and rewrite two of its three roles, since "theme, vendored under `themes/`" and "web server, serving the built site and the redirects" describe this repo's wiring where "static site generator" correctly describes the tool.
137+
-**Settled** - Blog is the only repo carrying a `3rd Party Tools` table today, and it needs both fixes the rule now states: drop the License column, which the audit reports since 2026-08-09, and rewrite all three roles, since "theme, vendored under `themes/`" and "web server, serving the built site and the redirects" describe this repo's wiring where "static site generator" describes the tool correctly and differs only by its opening capital and its full stop.
138138

139-
-**Bring each repo's `3rd Party Tools` entries onto the shared catalog.** Measured on 2026-08-08: 56 findings across four repos, every one of them a link, a description, or an ordering that disagrees with [`spec/third-party-tools.json`][third-party-tools].
139+
-**Bring each repo's `3rd Party Tools` entries onto the shared catalog.** Measured on 2026-08-09: 57 findings across four repos, a link, a description, or an ordering that disagrees with [`spec/third-party-tools.json`][third-party-tools], plus the one License column [`spec/readme-structure.md`][readme-structure] forbids.
140140
-**Blocked by** - Nothing.
141141
-**Issue** - None filed.
142-
-**Checked** - Every repo's default branch on 2026-08-08, with the hub read at its own `develop`, which now conforms.
143-
-**Settled** - The bulk is absent descriptions rather than wrong ones: 48 of the 56 are a tool listed with no description at all, across LanguageTags, MediaTools and PlexCleaner, and PlexCleaner alone accounts for 27. Of the remaining eight, three describe a tool differently from the catalog, four link it differently, and one is Blog listing Hugo, PaperMod, Caddy out of alphabetical order.
142+
-**Checked** - Every repo's default branch on 2026-08-09, with the hub read at its own `develop`, which now conforms.
143+
-**Settled** - The bulk is absent descriptions rather than wrong ones: 48 of the 57 are a tool listed with no description at all, across LanguageTags, MediaTools and PlexCleaner, and PlexCleaner alone accounts for 25 of those and 27 findings overall. Of the remaining nine, three describe a tool differently from the catalog, four link it differently, one is Blog listing Hugo, PaperMod, Caddy out of alphabetical order, and one is Blog's License column.
144144
-**Settled** - Twelve tools already appear in more than one repo, which is what makes the catalog worth having before the 17 repos owing the section write their own wording for each.
145145
-**Settled** - Four tools are already linked by two different URLs across the fleet, and the catalog picks one each: GitHub Actions takes `github.com/actions`, Dependabot takes `github.com/dependabot`, Nerdbank.GitVersioning takes the project repo rather than its marketplace action, and uv takes `docs.astral.sh/uv/` to match ruff. The hub was the outlier on the first two and is fixed.
146146
-**Settled** - PlexCleaner lists Bring Your Own Badge as a tool, so the retired badge service has a fourth touchpoint beyond the three rendering it, and that entry goes with the same deletion.

‎spec/audit.py‎

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -690,8 +690,32 @@ def readme_link_findings(text, model, slug):
690690
returnfindings
691691

692692

693-
_TOOL_ROW=re.compile(r"^\|\s*\[([^\]]+)\]\[([^\]]+)\]\s*\|\s*([^|]*?)\s*\|")
693+
# Both outer pipes are optional because GitHub's Markdown makes them optional, so a row written without either is still a row this section is graded on.
694+
# Requiring them skipped such a row outright, so its link, its description and its place in the ordering all went unread and the repo scored clean on a table nothing had read.
695+
# The separating pipe is what makes a row a row, and it is the one this keeps demanding, since a line carrying none of them is prose.
696+
_TOOL_ROW=re.compile(r"^\|?\s*\[([^\]]+)\]\[([^\]]+)\]\s*\|\s*([^|]*?)\s*(?:\||$)")
694697
_TOOL_BULLET=re.compile(r"^[-*]\s*\[([^\]]+)\]\[([^\]]+)\]\s*(.*)$")
698+
_DELIM_CELL=re.compile(r"^:?-{3,}:?$")
699+
700+
701+
deftable_cells(line):
702+
"""The cells of a Markdown table row, or None where the line is not one.
703+
704+
Both outer pipes are dropped where they are there, and neither is required, for the same reason _TOOL_ROW
705+
stopped requiring them.
706+
707+
A separating pipe is required instead, which is what keeps a thematic break out: `---` alone carries no
708+
pipe and returns None here, where treating it as a one-cell delimiter row would have read the paragraph
709+
above it as a table header.
710+
"""
711+
s=line.strip()
712+
if"|"notins:
713+
returnNone
714+
ifs.startswith("|"):
715+
s=s[1:]
716+
ifs.endswith("|"):
717+
s=s[:-1]
718+
return [c.strip() forcins.split("|")]
695719

696720

697721
defthird_party_tool_findings(text, catalog):
@@ -705,6 +729,11 @@ def third_party_tool_findings(text, catalog):
705729
706730
Matching is by the tool's display name, the text a README links, since that is what a reader compares
707731
across repos and what the catalog is keyed on.
732+
733+
The License column is read off the table's header rather than off its rows, so it reports once for the
734+
table instead of once per tool, and it is the only extra column named. spec/readme-structure.md forbids
735+
that one column and no other, and a check that flagged every extra column would be grading the fleet on a
736+
rule nobody wrote.
708737
"""
709738
body=readme_region(text, "3rd Party Tools")
710739
ifbodyisNone:
@@ -714,7 +743,14 @@ def third_party_tool_findings(text, catalog):
714743
defs= {m.group(1): m.group(2) formin_LINK_DEF.finditer(unfenced_text(text))}
715744
findings= []
716745
listed= []
717-
forlninbody.split("\n"):
746+
lines=body.split("\n")
747+
fori, lninenumerate(lines):
748+
cells=table_cells(ln)
749+
ifiandcellsandall(_DELIM_CELL.match(c) forcincells):
750+
header=table_cells(lines[i-1]) or []
751+
ifany(c.lower() =="license"forcinheader):
752+
findings.append(("LETTER", "readme: 3rd Party Tools carries a License column - a license belongs to the dependency and is authoritative at its source, so restating it adds a maintenance obligation and no information (spec/readme-structure.md)"))
753+
continue
718754
m=_TOOL_ROW.match(ln.strip()) or_TOOL_BULLET.match(ln.strip())
719755
ifnotm:
720756
continue
@@ -1804,6 +1840,25 @@ def _selftest():
18041840
# The list is alphabetized, which spec/readme-structure.md states and nothing checked until now.
18051841
("an unsorted tool list is reported", tools_head+"| Tool | Role |\n| --- | --- |\n| [markdownlint-cli2][md-link] | Markdown linter. |\n| [cspell][cspell-link] | Spell checker. |\n"+tools_defs+"[md-link]: https://github.com/DavidAnson/markdownlint-cli2\n", 1),
18061842
("a sorted tool list is not", tools_head+"| Tool | Role |\n| --- | --- |\n| [cspell][cspell-link] | Spell checker. |\n| [markdownlint-cli2][md-link] | Markdown linter. |\n"+tools_defs+"[md-link]: https://github.com/DavidAnson/markdownlint-cli2\n", 0),
1843+
# GitHub's Markdown makes both outer pipes optional, so a row written without either is an entry rather than a non-row.
1844+
# Requiring them read such a table as empty, which scores as clean rather than as unread.
1845+
("a row with no trailing pipe is read", tools_head+"| Tool | Role |\n| --- | --- |\n| [cspell][cspell-link] | Spell checker.\n"+tools_defs, 0),
1846+
("a row with no trailing pipe is judged like any other", tools_head+"| Tool | Role |\n| --- | --- |\n| [cspell][cspell-link] | Spell-checks the README in CI.\n"+tools_defs, 1),
1847+
("a whole table with no trailing pipes is ordered", tools_head+"| Tool | Role\n| --- | ---\n| [markdownlint-cli2][md-link] | Markdown linter.\n| [cspell][cspell-link] | Spell checker.\n"+tools_defs+"[md-link]: https://github.com/DavidAnson/markdownlint-cli2\n", 1),
1848+
("a row with no leading pipe is read", tools_head+"Tool | Role |\n--- | --- |\n[cspell][cspell-link] | Spell checker. |\n"+tools_defs, 0),
1849+
("a table with neither outer pipe is judged", tools_head+"Tool | Role\n--- | ---\n[cspell][cspell-link] | Spell-checks the README in CI.\n"+tools_defs, 1),
1850+
# The License column is forbidden by spec/readme-structure.md, and project-types.json asserted that as `letter` before anything read it.
1851+
# One finding is raised for the table, read off its header, rather than one per row.
1852+
("a License column is reported once", tools_head+"| Tool | Role | License |\n| --- | --- | --- |\n| [cspell][cspell-link] | Spell checker. | MIT |\n| [markdownlint-cli2][md-link] | Markdown linter. | MIT |\n"+tools_defs+"[md-link]: https://github.com/DavidAnson/markdownlint-cli2\n", 1),
1853+
("a License column does not stop the rows being read", tools_head+"| Tool | Role | License |\n| --- | --- | --- |\n| [cspell][cspell-link] | Spell-checks the README in CI. | MIT |\n"+tools_defs, 2),
1854+
("a table with no License column is not reported", tools_head+"| Tool | Role |\n| --- | --- |\n| [cspell][cspell-link] | Spell checker. |\n"+tools_defs, 0),
1855+
# A tool whose own role is the word license must not read as the column, since the header is a header.
1856+
("a row cell reading License is not the column", tools_head+"| Tool | Role |\n| --- | --- |\n| [Widget][widget-link] | License |\n"+tools_defs, 0),
1857+
("a License column with no outer pipes is reported", tools_head+"Tool | Role | License\n--- | --- | ---\n[cspell][cspell-link] | Spell checker. | MIT\n"+tools_defs, 1),
1858+
# A thematic break carries no pipe, so it is not a one-cell delimiter row and the paragraph above it is not a header.
1859+
("a thematic break is not a delimiter row", tools_head+"Licensing notes follow.\n\n---\n\n| Tool | Role |\n| --- | --- |\n| [cspell][cspell-link] | Spell checker. |\n"+tools_defs, 0),
1860+
# Prose mentioning a tool is not a row, since a link alone does not make one.
1861+
("prose naming a cataloged tool is not a row", tools_head+"We run [cspell][cspell-link] in CI.\n"+tools_defs, 0),
18071862
]
18081863
forlabel, text, wantnintool_cases:
18091864
got=third_party_tool_findings(text, cat)

0 commit comments

Comments
 (0)