move: source_type: Default streichen, unclassified als sichtbares Fach, layout: fuer source (schliesst #66)
Files changed: - CHANGES.md - VERSION - instructions/dev/corpus-policy.md - instructions/wiki-ingest/SKILL.md - kb/index.md - kb/log.md - kb/sources/COLLECTION.md - kb/sources/INDEX.md - kb/sources/Source - AMD Powermanagement CPU.md - kb/sources/Source - Arch Linux Cheat Sheet.md - kb/sources/Source - Conversation - AGENTS.md Skill Restructuring Session 2026-08-04.md - kb/sources/Source - Conversation - Auto Mode and Tool Choice Session 2026-08-31.md - kb/sources/Source - Conversation - Comma Bug Budget Refund and Lint Report Path Session 2026-08-31.md - kb/sources/Source - Conversation - ENVIRONMENT.md as an Optional Third Session-Level File Session 2026-08-31.md - kb/sources/Source - Conversation - Gate Counting and Measured Calibration Session 2026-08-31.md - kb/sources/Source - Conversation - Hardening the Test Suite Against Silent Environment Dependencies Session 2026-08-31.md - kb/sources/Source - Conversation - Issue Triage Labels and TODO Retirement Session 2026-08-31.md - kb/sources/Source - Conversation - Nightly Drift-Check Workflow and doctor's Bootstrap Gap Session 2026-08-31.md - kb/sources/Source - Conversation - Two Round-Trip Defects Found by an Ingest Session 2026-08-31.md - kb/sources/Source - Conversation - Versioning CI-CD and Content Migration Session 2026-08-30.md - kb/sources/Source - Conversation - Write-Once Frontmatter Fields and touch --set Session 2026-08-31.md - kb/sources/Source - Copilot Skill Restructure Instructions.md - kb/sources/Source - Docker Cheatsheet.md - kb/sources/Source - Gitea Issue 41 - Issue Management and Label Scheme 2026-09-02.md - kb/sources/Source - Gitea Issues 62-63 - status-incoming Label Introduction 2026-09-04.md - kb/sources/Source - LLM Improvements Codex Analysis.md - kb/sources/Source - LLM Improvements Production Agent Gaps 2026.md - kb/sources/Source - LLM Improvements Sonnet Analysis.md - kb/sources/Source - LLM Wiki Pattern.md - kb/sources/Source - LLM Wiki v2.md - kb/sources/Source - MCP Read Server Implementation Session 2026-09-02.md - kb/sources/Source - Private-Instance Merge Correction and Issue 30 Session 2026-09-01.md - kb/sources/Source - Public Release, Corpus Purge and History Squash Session 2026-09-01.md - kb/sources/Source - Publish-Remote Gate and Issue Triage Session 2026-09-01.md - kb/sources/Source - Version Part Nomenclature and Breaking Change Gate Session 2026-09-02.md - kb/sources/Source - Wine.md - kb/sources/Source - qmd - GitHub Repository.md - kb/sources/analyses/Source - Copilot Skill Restructure Instructions.md - kb/sources/analyses/Source - LLM Improvements Codex Analysis.md - kb/sources/analyses/Source - LLM Improvements Production Agent Gaps 2026.md - kb/sources/analyses/Source - LLM Improvements Sonnet Analysis.md - kb/sources/articles/Source - AMD Powermanagement CPU.md - kb/sources/articles/Source - LLM Wiki Pattern.md - kb/sources/articles/Source - LLM Wiki v2.md - kb/sources/documents/Source - qmd - GitHub Repository.md - kb/sources/notes/Source - Arch Linux Cheat Sheet.md - kb/sources/notes/Source - Docker Cheatsheet.md - kb/sources/notes/Source - Wine.md - kb/sources/trackers/Source - Gitea Issue 41 - Issue Management and Label Scheme 2026-09-02.md - kb/sources/trackers/Source - Gitea Issues 62-63 - status-incoming Label Introduction 2026-09-04.md - kb/sources/transcripts/Source - Conversation - AGENTS.md Skill Restructuring Session 2026-08-04.md - kb/sources/transcripts/Source - Conversation - Auto Mode and Tool Choice Session 2026-08-31.md - kb/sources/transcripts/Source - Conversation - Comma Bug Budget Refund and Lint Report Path Session 2026-08-31.md - kb/sources/transcripts/Source - Conversation - ENVIRONMENT.md as an Optional Third Session-Level File Session 2026-08-31.md - kb/sources/transcripts/Source - Conversation - Gate Counting and Measured Calibration Session 2026-08-31.md - kb/sources/transcripts/Source - Conversation - Hardening the Test Suite Against Silent Environment Dependencies Session 2026-08-31.md - kb/sources/transcripts/Source - Conversation - Issue Triage Labels and TODO Retirement Session 2026-08-31.md - kb/sources/transcripts/Source - Conversation - Nightly Drift-Check Workflow and doctor's Bootstrap Gap Session 2026-08-31.md - kb/sources/transcripts/Source - Conversation - Two Round-Trip Defects Found by an Ingest Session 2026-08-31.md - kb/sources/transcripts/Source - Conversation - Versioning CI-CD and Content Migration Session 2026-08-30.md - kb/sources/transcripts/Source - Conversation - Write-Once Frontmatter Fields and touch --set Session 2026-08-31.md - kb/sources/transcripts/Source - MCP Read Server Implementation Session 2026-09-02.md - kb/sources/transcripts/Source - Private-Instance Merge Correction and Issue 30 Session 2026-09-01.md - kb/sources/transcripts/Source - Public Release, Corpus Purge and History Squash Session 2026-09-01.md - kb/sources/transcripts/Source - Publish-Remote Gate and Issue Triage Session 2026-09-01.md - kb/sources/transcripts/Source - Version Part Nomenclature and Breaking Change Gate Session 2026-09-02.md - tools/CONTRACT.md - tools/chemenu/lint_core.py - tools/chemenu/tests/conftest.py - tools/chemenu/tests/test_lint.py - tools/chemenu/tests/test_new_page.py - tools/chemenu/tests/test_provenance.py - tools/chemenu/tests/test_touch.py - tools/chemenu/tests/test_type_resolver.py - tools/chemenu/tests/test_xref.py - types/source.md - types/source.schema.yaml - types/type-spec.md - work/reclassify-source-types/README.md - work/reclassify-source-types/plan.md
This commit is contained in:
@@ -127,6 +127,22 @@ def misplaced_pages(pages: dict[str, Page]) -> list[dict]:
|
||||
]
|
||||
|
||||
|
||||
def unclassified_source_pages(pages: dict[str, Page]) -> list[dict]:
|
||||
"""Source pages sitting in the `unclassified` catalog slot.
|
||||
|
||||
Advisory, like `misplaced_pages` above: `unclassified` is the visible
|
||||
fallback for a genuinely unclear source (Gitea #66, replacing the old
|
||||
silent `default: notes`), not a broken state - a page there is exactly as
|
||||
valid as one anywhere else, only waiting for someone to look at it and
|
||||
pick a real category with `wikitool touch --set source_type=<value>`."""
|
||||
return [
|
||||
{"page": title}
|
||||
for title, page in sorted(pages.items())
|
||||
if page.frontmatter.get("type") == "types/source.md"
|
||||
and page.frontmatter.get("source_type") == "unclassified"
|
||||
]
|
||||
|
||||
|
||||
def nested_pages(kb_dir: Path, pages: dict[str, Page]) -> list[dict]:
|
||||
"""Report form of `find_nested_pages`: `{"page", "at", "depth"}` per
|
||||
finding.
|
||||
@@ -474,6 +490,7 @@ def run_lint(kb_dir: Path) -> dict:
|
||||
"misplaced_pages": misplaced,
|
||||
"nested_pages": nested,
|
||||
"unsharded_collections": unsharded_collections(kb_dir, pages),
|
||||
"unclassified_source_pages": unclassified_source_pages(pages),
|
||||
"uncovered_raw_files": find_uncovered_raw_files(config.RAW_DIR, pages),
|
||||
"broken_raw_refs": find_broken_raw_refs(pages),
|
||||
"duplicate_raw_file_owners": find_duplicate_raw_file_owners(pages),
|
||||
@@ -567,6 +584,12 @@ def render_markdown(report: dict) -> str:
|
||||
"`wikitool move --reconcile` and `wikitool index rebuild`"
|
||||
),
|
||||
)
|
||||
_section(
|
||||
lines, "Unclassified Source Pages (source_type: unclassified) - recommendation, not an error",
|
||||
report.get("unclassified_source_pages", []),
|
||||
lambda i: f"[[{i['page']}]] - `wikitool touch --set source_type=<value>` once its "
|
||||
"category is known",
|
||||
)
|
||||
_section(
|
||||
lines, "Uncovered Raw Files (no source page)", report["uncovered_raw_files"],
|
||||
lambda i: f"`{i}`",
|
||||
@@ -728,6 +751,12 @@ def default_report_path(report: dict) -> Path:
|
||||
# Failing on it would also make `lint` red on a corpus that is entirely
|
||||
# self-consistent, which is the state the recommendation is asking to improve.
|
||||
#
|
||||
# `unclassified_source_pages` is advisory for the same reason `misplaced_pages`
|
||||
# is: `unclassified` (Gitea #66) is a deliberately visible catalog slot for a
|
||||
# source whose category is genuinely unclear, not a defect - failing on it
|
||||
# would penalise the honest "I don't know yet" that the slot exists to allow,
|
||||
# where the old silent `default: notes` hid the same uncertainty for free.
|
||||
#
|
||||
# `malformed_edges` and `unbalanced_markers` are hard from the start: neither
|
||||
# describes an unconverted page, only a broken one.
|
||||
#
|
||||
|
||||
@@ -311,7 +311,7 @@ def kb_dir(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
|
||||
"\n# Modbus\n\n## Definition\n\nIndustrial protocol.\n",
|
||||
)
|
||||
write_page(
|
||||
kb / "sources/Source - Aurora.md",
|
||||
kb / "sources/notes/Source - Aurora.md",
|
||||
{
|
||||
"type": "types/source.md", "source_type": "notes", "author": "Torben",
|
||||
"source": "raw/notes/Aurora.md", "date": "2026-08-02",
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import json
|
||||
import shutil
|
||||
from datetime import date
|
||||
|
||||
import pytest
|
||||
|
||||
from chemenu import config, kb_state
|
||||
from chemenu.type_resolver import resolver
|
||||
from chemenu.commands.lint import (
|
||||
HARD_ERROR_KEYS,
|
||||
hard_error_keys,
|
||||
@@ -98,7 +100,7 @@ def test_lint_detects_dangling_source_page_entity_ref(kb_dir):
|
||||
"""Source pages declare `entities:`/`concepts:`, not `related:`/`sources:` -
|
||||
the field list comes from each type-spec's own `page_ref_fields:`."""
|
||||
write_page(
|
||||
kb_dir / "sources/Source - Aurora.md",
|
||||
kb_dir / "sources/notes/Source - Aurora.md",
|
||||
{"type": "types/source.md", "source_type": "notes", "author": "Torben",
|
||||
"source": "raw/notes/Aurora.md", "date": "2026-08-02", "tags": [],
|
||||
"entities": ["aurora", "ghost-entity"], "concepts": []},
|
||||
@@ -293,16 +295,59 @@ def test_lint_recommends_areas_for_a_collection_past_the_threshold(kb_dir):
|
||||
assert finding["layout_declared"] is True
|
||||
|
||||
|
||||
def test_the_area_recommendation_says_the_layout_is_missing_when_it_is(kb_dir):
|
||||
"""`types/source.md` deliberately declares no `layout:`, so a sources
|
||||
collection that grew past the threshold has nothing for `move` to compute a
|
||||
destination from - the fix starts one step earlier, and the report says so."""
|
||||
def test_the_area_recommendation_says_the_layout_is_missing_when_it_is(kb_dir, tmp_path, monkeypatch):
|
||||
"""A collection whose type has a `subtype_field:` but no `layout:` has
|
||||
nothing for `move` to compute a destination from - the fix starts one step
|
||||
earlier, and the report says so.
|
||||
|
||||
`types/source.md` used to be the worked example, but Gitea #66 gave it a
|
||||
`layout:` covering every enum value, and `comparison` (the other type with
|
||||
no `layout:`) has no `subtype_field:` at all to exercise this branch with.
|
||||
So this is now the one place in the suite that still needs the
|
||||
"has a subtype axis, hasn't been given a layout" shape, which is a fixture
|
||||
type-spec, copied in beside the real ones so every other fixture page
|
||||
(entities, concepts, sources, comparisons) keeps resolving normally.
|
||||
|
||||
Filled into `comparisons/` rather than `sources/`: the fixture wiki's own
|
||||
source page now lives correctly under `sources/notes/`, and reusing that
|
||||
directory here would mix its real `types/source.md` pages into this
|
||||
fixture type's counts - `comparisons/` stays empty in the base fixture,
|
||||
so filling it is self-contained."""
|
||||
types_dir = tmp_path / "types"
|
||||
shutil.copytree(config._PACKAGE_ROOT / "types", types_dir)
|
||||
(types_dir / "badtype.md").write_text(
|
||||
"---\n"
|
||||
"type: types/type-spec.md\n"
|
||||
"name: badtype\n"
|
||||
"description: fixture type with a subtype field and no layout\n"
|
||||
"schema: types/badtype.schema.yaml\n"
|
||||
"subtype_field: bad_type\n"
|
||||
"base_dir: badthings\n"
|
||||
"---\n\n# badtype\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
(types_dir / "badtype.schema.yaml").write_text(
|
||||
"type: object\n"
|
||||
"properties:\n"
|
||||
" type: {type: string, const: 'types/badtype.md'}\n"
|
||||
" bad_type: {type: string}\n"
|
||||
" created: {type: string, format: date}\n"
|
||||
" modified: {type: string, format: date}\n"
|
||||
" provenance: {type: string, enum: [sourced, general, mixed]}\n"
|
||||
" summary: {type: string}\n"
|
||||
"required: [type, bad_type, created, modified, provenance, summary]\n"
|
||||
"additionalProperties: false\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
monkeypatch.setattr(config, "TYPES_DIR", types_dir)
|
||||
monkeypatch.setattr(resolver, "_repo_root", tmp_path)
|
||||
|
||||
_fill_collection(
|
||||
kb_dir, "sources", "types/source.md", "source_type",
|
||||
kb_dir, "comparisons", "types/badtype.md", "bad_type",
|
||||
{"notes": 30, "article": 21},
|
||||
)
|
||||
report = run_lint(kb_dir)
|
||||
finding = next(i for i in report["unsharded_collections"] if i["collection"] == "sources")
|
||||
finding = next(i for i in report["unsharded_collections"] if i["collection"] == "comparisons")
|
||||
assert finding["layout_declared"] is False
|
||||
assert "Declare a `layout:`" in render_markdown(report)
|
||||
|
||||
@@ -323,10 +368,12 @@ def test_the_area_recommendation_is_not_a_failure(kb_dir):
|
||||
|
||||
|
||||
def test_lint_is_silent_about_a_collection_under_the_threshold(kb_dir):
|
||||
"""The sources shape: 29 pages, lopsided across `source_type` - and under
|
||||
the threshold anyway, so it never fires. That is what keeps the bad split
|
||||
(one area of 25 plus four splinters) from ever being recommended, without
|
||||
the check needing to know anything about sources."""
|
||||
"""The pre-#66 sources shape: 29 pages, lopsided across `source_type`
|
||||
because the schema handed out `notes` as a default whenever nobody chose
|
||||
a value - and under the threshold anyway, so it never fires regardless.
|
||||
That is what kept the bad split (one area of 25 plus four splinters) from
|
||||
ever being recommended while the default still existed, without the check
|
||||
needing to know anything about sources."""
|
||||
_fill_collection(
|
||||
kb_dir, "sources", "types/source.md", "source_type",
|
||||
{"notes": 25, "article": 3, "document": 1},
|
||||
|
||||
@@ -43,8 +43,11 @@ def test_page_subdir_falls_back_for_unmapped_subtype():
|
||||
|
||||
|
||||
def test_page_subdir_is_none_for_types_without_layout():
|
||||
assert _page_subdir(None, "types/source.md") is None
|
||||
assert _page_subdir("anything", "types/source.md") is None
|
||||
# `comparison` has no `layout:` at all (and no `subtype_field:` to have one
|
||||
# for) - `types/source.md` no longer qualifies, since Gitea #66 gave it a
|
||||
# `layout:` covering every enum value.
|
||||
assert _page_subdir(None, "types/comparison.md") is None
|
||||
assert _page_subdir("anything", "types/comparison.md") is None
|
||||
|
||||
|
||||
def test_coerce_set_value_uses_declared_schema_type():
|
||||
@@ -190,21 +193,42 @@ def test_new_source_prefixes_title_and_prefills_related_entities(monkeypatch, kb
|
||||
_fixture_raw_file(monkeypatch, kb_dir, "raw/notes/gateway.example.net.md")
|
||||
result = _invoke_new(monkeypatch, kb_dir, [
|
||||
"new", "source", "--name", "gateway.example.net",
|
||||
"--set", "source_type=notes",
|
||||
"--set", "raw_files=raw/notes/gateway.example.net.md",
|
||||
"--set", "entities=aurora,Borealis",
|
||||
])
|
||||
assert result.exit_code == 0, result.output
|
||||
path = kb_dir / "sources/Source - gateway.example.net.md"
|
||||
# Gitea #66: `types/source.md` now declares a `layout:`, so `source_type:
|
||||
# notes` lands in its area - the same placement rule an entity or concept
|
||||
# already follows.
|
||||
path = kb_dir / "sources/notes/Source - gateway.example.net.md"
|
||||
assert path.exists()
|
||||
fm, body = read_page(path)
|
||||
assert fm["type"] == "types/source.md"
|
||||
assert fm["entities"] == ["aurora", "Borealis"]
|
||||
assert fm["author"] == "Torben" # WIKI_AUTHOR override
|
||||
assert fm["source_type"] == "notes" # schema default
|
||||
assert fm["source_type"] == "notes"
|
||||
assert "[[aurora]]" in body
|
||||
assert "[[Borealis]]" in body
|
||||
|
||||
|
||||
def test_new_source_rejects_missing_source_type(monkeypatch, kb_dir):
|
||||
"""Gitea #66: `source_type` lost its `default: notes` - `new` must refuse
|
||||
rather than silently picking a value, and must not write a file or create
|
||||
a directory while refusing."""
|
||||
monkeypatch.setenv("WIKI_AUTHOR", "Torben") # isolate the refusal from the author check
|
||||
_fixture_raw_file(monkeypatch, kb_dir, "raw/notes/gateway.example.net.md")
|
||||
result = _invoke_new(monkeypatch, kb_dir, [
|
||||
"new", "source", "--name", "no-source-type",
|
||||
"--set", "raw_files=raw/notes/gateway.example.net.md",
|
||||
])
|
||||
assert result.exit_code != 0
|
||||
assert "source_type" in result.output
|
||||
# No page written anywhere under sources/ - not even into an area that
|
||||
# legitimately pre-exists (the fixture wiki's own `sources/notes/`).
|
||||
assert not list(kb_dir.glob("sources/**/Source - no-source-type.md"))
|
||||
|
||||
|
||||
def test_new_source_author_falls_back_to_git_config(monkeypatch, kb_dir):
|
||||
"""No WIKI_AUTHOR set - default_author() falls back to `git config
|
||||
user.name`, run with cwd=config.ROOT.
|
||||
@@ -225,10 +249,11 @@ def test_new_source_author_falls_back_to_git_config(monkeypatch, kb_dir):
|
||||
_fixture_raw_file(monkeypatch, kb_dir, "raw/notes/gateway.example.net.md")
|
||||
result = _invoke_new(monkeypatch, kb_dir, [
|
||||
"new", "source", "--name", "git-config-author",
|
||||
"--set", "source_type=notes",
|
||||
"--set", "raw_files=raw/notes/gateway.example.net.md",
|
||||
])
|
||||
assert result.exit_code == 0, result.output
|
||||
fm, _body = read_page(kb_dir / "sources/Source - git-config-author.md")
|
||||
fm, _body = read_page(kb_dir / "sources/notes/Source - git-config-author.md")
|
||||
assert fm["author"] == "Fixture Author"
|
||||
|
||||
|
||||
@@ -336,6 +361,7 @@ def test_raw_files_error_points_at_the_comma_split(monkeypatch, kb_dir, raw_dir)
|
||||
monkeypatch.setattr(config, "ROOT", kb_dir.parent)
|
||||
result = _invoke_new(monkeypatch, kb_dir, [
|
||||
"new", "source", "--name", "Split Path",
|
||||
"--set", "source_type=notes",
|
||||
"--set", "raw_files=raw/notes/Versioning, CI-CD.md",
|
||||
])
|
||||
assert result.exit_code == 1
|
||||
@@ -358,5 +384,5 @@ def test_source_page_accepts_a_raw_file_whose_name_has_a_comma(monkeypatch, kb_d
|
||||
"--set", "source_type=notes",
|
||||
])
|
||||
assert result.exit_code == 0, result.output
|
||||
frontmatter, _ = read_page(kb_dir / "sources/Source - Comma Source.md")
|
||||
frontmatter, _ = read_page(kb_dir / "sources/notes/Source - Comma Source.md")
|
||||
assert frontmatter["raw_files"] == ["raw/notes/Versioning, CI-CD.md"]
|
||||
|
||||
@@ -400,10 +400,11 @@ def test_new_source_with_multiple_raw_files(kb_dir, raw_dir, monkeypatch):
|
||||
|
||||
result = runner.invoke(app, [
|
||||
"new", "source", "--name", "Multi",
|
||||
"--set", "source_type=notes",
|
||||
"--set", "raw_files=raw/notes/Aurora.md,raw/notes/Second.md",
|
||||
])
|
||||
assert result.exit_code == 0, result.output
|
||||
fm, _body = read_page(kb_dir / "sources/Source - Multi.md")
|
||||
fm, _body = read_page(kb_dir / "sources/notes/Source - Multi.md")
|
||||
assert fm["raw_files"] == ["raw/notes/Aurora.md", "raw/notes/Second.md"]
|
||||
|
||||
|
||||
@@ -483,7 +484,7 @@ def test_lint_does_not_flag_source_page_self_citation(kb_dir, raw_dir, monkeypat
|
||||
|
||||
refs, block = _footnote_block(("Source - Aurora", "Aurora.md"))
|
||||
write_page(
|
||||
kb_dir / "sources/Source - Aurora.md",
|
||||
kb_dir / "sources/notes/Source - Aurora.md",
|
||||
{
|
||||
"type": "source", "source_type": "notes", "author": "Torben",
|
||||
"raw_files": ["raw/notes/Aurora.md"], "date": "2026-08-02",
|
||||
|
||||
@@ -82,7 +82,7 @@ def test_touch_uses_date_field_for_source_pages(touch_wiki):
|
||||
"""Source pages declare `date:`, not `modified:` - the field comes from
|
||||
the type's schema rather than a hardcoded name."""
|
||||
_touch(page_title="Source - Aurora", date="2026-08-13")
|
||||
frontmatter, _ = read_page(touch_wiki / "sources/Source - Aurora.md")
|
||||
frontmatter, _ = read_page(touch_wiki / "sources/notes/Source - Aurora.md")
|
||||
assert str(frontmatter["date"]) == "2026-08-13"
|
||||
assert "modified" not in frontmatter
|
||||
|
||||
@@ -92,9 +92,9 @@ def test_touch_leaves_a_sources_publication_date_alone(touch_wiki):
|
||||
record of when the page was last edited. Auto-bumping it to today replaced a
|
||||
fact about the world and left the page contradicting the date printed in its
|
||||
own body - so it moves only on an explicit --date."""
|
||||
before, _ = read_page(touch_wiki / "sources/Source - Aurora.md")
|
||||
before, _ = read_page(touch_wiki / "sources/notes/Source - Aurora.md")
|
||||
_touch(page_title="Source - Aurora", summary="Neue Zusammenfassung")
|
||||
frontmatter, _ = read_page(touch_wiki / "sources/Source - Aurora.md")
|
||||
frontmatter, _ = read_page(touch_wiki / "sources/notes/Source - Aurora.md")
|
||||
assert frontmatter["summary"] == "Neue Zusammenfassung"
|
||||
assert str(frontmatter["date"]) == str(before["date"])
|
||||
|
||||
@@ -221,7 +221,7 @@ def test_set_raw_files_accepts_an_existing_path_with_a_comma(touch_wiki, raw_dir
|
||||
page_title="Source - Aurora",
|
||||
set_fields=[r"raw_files=raw/notes/Versioning\, CI-CD.md"],
|
||||
)
|
||||
frontmatter, _ = read_page(touch_wiki / "sources/Source - Aurora.md")
|
||||
frontmatter, _ = read_page(touch_wiki / "sources/notes/Source - Aurora.md")
|
||||
assert frontmatter["raw_files"] == ["raw/notes/Versioning, CI-CD.md"]
|
||||
|
||||
|
||||
|
||||
@@ -108,13 +108,33 @@ def test_concept_layout_covers_every_declared_concept_type():
|
||||
assert all(spec.get("title") for spec in layout.values())
|
||||
|
||||
|
||||
def test_source_layout_covers_every_declared_source_type():
|
||||
"""Gitea #66: `source_type` had a silent `default: notes` that made the
|
||||
collection lopsided (25 of 29 pages), which is what made #59 leave
|
||||
`source` flat. Removing the default and reclassifying by what the pages
|
||||
actually are turned it into six real areas - this is the concept-layout
|
||||
test's counterpart for `source`, checking the same enum/layout agreement."""
|
||||
layout = resolver.get_layout("types/source.md")
|
||||
assert layout is not None
|
||||
assert set(layout) == set(resolver.get_enum("types/source.md", "source_type"))
|
||||
assert {key: spec["dir"] for key, spec in layout.items()} == {
|
||||
"transcript": "transcripts",
|
||||
"analysis": "analyses",
|
||||
"article": "articles",
|
||||
"document": "documents",
|
||||
"notes": "notes",
|
||||
"tracker": "trackers",
|
||||
"unclassified": "unclassified",
|
||||
}
|
||||
assert all(spec.get("title") for spec in layout.values())
|
||||
|
||||
|
||||
def test_get_layout_is_none_for_types_without_one():
|
||||
"""`comparison` has no subtype field at all; `source` has one and
|
||||
deliberately declares no `layout:` anyway - 25 of its 29 pages carry the
|
||||
same `source_type`, so splitting on it would make one area and four
|
||||
splinters (Gitea #59). Having a subtype axis is not a reason to use it."""
|
||||
"""`comparison` has no subtype field at all, so there is nothing a
|
||||
`layout:` could key on - the worked example for a type that stays flat
|
||||
because the axis itself doesn't exist (as opposed to `source` pre-#66,
|
||||
which had the axis but a lopsided distribution - see #59)."""
|
||||
assert resolver.get_layout("types/comparison.md") is None
|
||||
assert resolver.get_layout("types/source.md") is None
|
||||
|
||||
|
||||
def test_get_layout_rejects_a_dir_with_a_path_separator(tmp_path):
|
||||
@@ -262,7 +282,9 @@ def test_subtype_dir_falls_back_for_unmapped_subtype():
|
||||
|
||||
|
||||
def test_subtype_dir_is_none_without_layout_or_subtype():
|
||||
assert resolver.subtype_dir("types/source.md", "notes") is None
|
||||
# `comparison` has no `layout:` (and no `subtype_field:` to have one for)
|
||||
# - the value passed is arbitrary, since the type has no subtype axis at all.
|
||||
assert resolver.subtype_dir("types/comparison.md", "whatever") is None
|
||||
assert resolver.subtype_dir("types/entity.md", None) is None
|
||||
|
||||
|
||||
@@ -272,8 +294,8 @@ def test_compute_target_dir_applies_layout_subdirectory():
|
||||
|
||||
|
||||
def test_compute_target_dir_is_flat_for_a_type_without_layout():
|
||||
target = resolver.compute_target_dir("types/source.md", {"source_type": "notes"})
|
||||
assert target == config.KB_DIR / "sources"
|
||||
target = resolver.compute_target_dir("types/comparison.md", {})
|
||||
assert target == config.KB_DIR / "comparisons"
|
||||
|
||||
|
||||
def test_compute_target_dir_resolves_against_repo_root_for_root_repo_types():
|
||||
|
||||
@@ -426,7 +426,7 @@ def test_xref_remove_clears_an_undeclared_leftover_field(kb_dir):
|
||||
page that already carries one is a dead end."""
|
||||
from chemenu.frontmatter_io import write_page
|
||||
|
||||
path = kb_dir / "sources/Source - Aurora.md"
|
||||
path = kb_dir / "sources/notes/Source - Aurora.md"
|
||||
frontmatter, body = read_page(path)
|
||||
frontmatter["related"] = ["aurora"]
|
||||
write_page(path, frontmatter, body)
|
||||
@@ -472,7 +472,7 @@ def test_xref_link_source_is_idempotent_on_the_source_page(kb_dir):
|
||||
|
||||
def test_xref_link_source_dry_run_leaves_the_source_page_alone(kb_dir):
|
||||
runner, app = _runner_env(kb_dir)
|
||||
path = kb_dir / "sources/Source - Aurora.md"
|
||||
path = kb_dir / "sources/notes/Source - Aurora.md"
|
||||
before = path.read_text(encoding="utf-8")
|
||||
runner.invoke(
|
||||
app,
|
||||
|
||||
Reference in New Issue
Block a user