Link-Katalog: authored/alternative-to/addresses, entity→entity-Lineage, Lint-Befund redundant_see_also (4.7.0, #43 #49)
CI / verify (push) Successful in 1m2s
Release / release (push) Successful in 37s

Files changed:
- CHANGES.md
- VERSION
- instructions/link-taxonomy.md
- kb/concepts/COLLECTION.md
- kb/entities/COLLECTION.md
- kb/entities/people/Andrej Karpathy.md
- kb/entities/people/Vannevar Bush.md
- tools/chemenu/evals/scorecard.py
- tools/chemenu/links.py
- tools/chemenu/lint_core.py
- tools/chemenu/tests/test_lint.py
This commit is contained in:
2026-09-04 18:44:22 +02:00
parent 72b2b4424f
commit 3916cb9541
11 changed files with 285 additions and 16 deletions
+43 -1
View File
@@ -181,6 +181,26 @@ def run_lint(kb_dir: Path) -> dict:
malformed_edges: list[dict] = []
unlabelled_edges: list[dict] = []
unauthorised_labels: list[dict] = []
redundant_see_also: list[dict] = []
# Every specific thing any page asserts about any pair, collected before the
# loop below because the reverse direction of an edge is not known while
# standing on the page that carries it.
#
# What it is for: `see-also` is the catalogue's declared last resort - it
# asserts that nothing better fit. When the *other* page already says
# something specific about the same pair (`Wine GE depends-on Wine` opposite
# `Wine see-also Wine GE`), the weak edge adds nothing a reader did not
# have: direction is authored but the inbound view is rendered, so the
# labelled edge already shows on both pages. Measured once on this corpus,
# that was 57 of 180 `see-also` edges - the largest single class, and none
# of it a vocabulary gap.
typed_edges: dict[tuple[str, str], str] = {}
for source_title, source_page in pages.items():
for edge in links.edges(source_page.frontmatter, "related"):
if edge.is_labelled and edge.label != links.SEE_ALSO:
typed_edges[(source_title, edge.target)] = edge.label
for title, page in sorted(pages.items()):
type_path = page.frontmatter.get("type")
if not type_path:
@@ -211,6 +231,16 @@ def run_lint(kb_dir: Path) -> dict:
if not edge.is_labelled:
unlabelled_edges.append({"page": title, "target": edge.target})
continue
if edge.label == links.SEE_ALSO:
reverse_label = typed_edges.get((edge.target, title))
if reverse_label is not None:
redundant_see_also.append(
{
"page": title,
"target": edge.target,
"reverse_label": reverse_label,
}
)
target_page = pages.get(edge.target)
if source_collection is None or target_page is None:
continue
@@ -301,6 +331,7 @@ def run_lint(kb_dir: Path) -> dict:
"malformed_edges": malformed_edges,
"unlabelled_edges": unlabelled_edges,
"unauthorised_labels": unauthorised_labels,
"redundant_see_also": redundant_see_also,
"unbalanced_markers": unbalanced_marker_findings,
"quote_limit_violations": quote_limit_violations,
"invalid_type_paths": invalid_type_paths,
@@ -403,6 +434,11 @@ def render_markdown(report: dict) -> str:
report.get("unauthorised_labels", []),
lambda i: f"[[{i['page']}]] `{i['label']}` -> kb/{i['destination']}/ ([[{i['target']}]])",
)
_section(
lines, "Redundant see-also (the other page already says something specific)",
report.get("redundant_see_also", []),
lambda i: f"[[{i['page']}]] `see-also` -> [[{i['target']}]], but [[{i['target']}]] already asserts `{i['reverse_label']}` back - drop the weaker edge, the inbound view renders the other one here",
)
_section(
lines, "Dangling Frontmatter References", report["dangling_frontmatter_refs"],
lambda i: f"[[{i['page']}]] `{i['field']}:` names `{i['target']}`, which is not a page",
@@ -485,7 +521,13 @@ def default_report_path(report: dict) -> Path:
# Findings that make a tree structurally wrong rather than merely untidy.
# `orphan_pages` is deliberately absent: many pages are validly reachable
# through the index or navigation only. `quote_limit_violations` is advisory
# too - it flags a habit, not a broken tree.
# too - it flags a habit, not a broken tree. `redundant_see_also` joins them for
# both of those reasons at once: a weak edge beside a specific one is redundant
# rather than wrong, and the check arrived long after the corpora it judges, so
# promoting it would turn every existing instance red on the upgrade that
# shipped it. Unlike `unlabelled_edges` it is not migration-gated either - there
# is no version at which the redundancy becomes an error, only a sweep someone
# does or does not get to.
#
# `malformed_edges` and `unbalanced_markers` are hard from the start: neither
# describes an unconverted page, only a broken one.