c64479fe02
Files changed: - AGENTS.md - CHANGES.md - ENVIRONMENT.md.template - SOUL.md - SOUL.md.template - USER.md.template - VERSION - docs/ownership-and-templates.md - docs/version-model.md - instructions/CONTRACT.md - instructions/dev/doc-pull-through.md - instructions/dev/stack-close/SKILL.md - instructions/dev/stack-dev/SKILL.md - instructions/dev/version-parts.md - instructions/setup-instance.md - kb/CONVENTIONS.md - kb/CONVENTIONS.md.template - kb/concepts/COLLECTION.md - kb/sources/COLLECTION.md - tools/CONTRACT.md - tools/chemenu/commands/types_cmd.py - tools/chemenu/commands/version_cmd.py - tools/chemenu/tests/test_toc.py - tools/chemenu/tests/test_version_cmd.py - tools/chemenu/toc.py - tools/chemenu/version.py - types/comparison.md - types/concept.md - types/entity.md - types/lint-report.md - types/source.md
200 lines
7.4 KiB
Python
200 lines
7.4 KiB
Python
from chemenu import blocks, toc
|
|
|
|
|
|
def _body(n_lines: int, sections: int) -> str:
|
|
"""A synthetic `# Title` + intro + `sections` `##` headings, `n_lines` long."""
|
|
lines = ["# Title", "", "Intro paragraph."]
|
|
per_section = max(1, (n_lines - len(lines) - sections) // sections) if sections else 0
|
|
for i in range(sections):
|
|
lines += ["", f"## Section {i}", ""]
|
|
lines += [f"Body line {j}." for j in range(per_section)]
|
|
while len(lines) < n_lines:
|
|
lines.append("Padding.")
|
|
return "\n".join(lines[:n_lines]) + "\n"
|
|
|
|
|
|
def test_needs_toc_is_false_at_the_threshold():
|
|
body = _body(toc.THRESHOLD, 3)
|
|
assert len(body.splitlines()) == toc.THRESHOLD
|
|
assert not toc.needs_toc(body)
|
|
|
|
|
|
def test_needs_toc_is_true_just_over_the_threshold():
|
|
body = _body(toc.THRESHOLD + 1, 3)
|
|
assert toc.needs_toc(body)
|
|
|
|
|
|
def test_needs_toc_measures_without_an_existing_region():
|
|
"""A file that shrank back under the threshold but still carries a stale
|
|
TOC region from when it was much longer must not count the region's own
|
|
lines toward keeping itself around - `needs_toc` strips it before
|
|
measuring. A 150-entry region is long enough on its own to push the
|
|
file over the threshold if it were (wrongly) counted."""
|
|
huge_region = toc.render([toc.Heading(level=2, text=f"Section {i}") for i in range(150)])
|
|
shrunk_with_stale_region = (
|
|
"# Title\n\nIntro paragraph.\n\n" + huge_region + "\n\n## Only Section\n\nOne line.\n"
|
|
)
|
|
assert len(shrunk_with_stale_region.splitlines()) > toc.THRESHOLD
|
|
assert not toc.needs_toc(shrunk_with_stale_region)
|
|
|
|
|
|
def test_iter_headings_skips_fenced_code():
|
|
body = "\n".join(
|
|
[
|
|
"# Title",
|
|
"",
|
|
"## Real Section",
|
|
"",
|
|
"```bash",
|
|
"# Not a heading",
|
|
"## Also not a heading",
|
|
"```",
|
|
"",
|
|
"### Real Subsection",
|
|
]
|
|
)
|
|
headings = toc.iter_headings(body)
|
|
assert [h.text for h in headings] == ["Real Section", "Real Subsection"]
|
|
assert [h.level for h in headings] == [2, 3]
|
|
|
|
|
|
def test_iter_headings_preserves_inline_code_in_heading_text():
|
|
"""Masking blanks inline code spans for *detection*, but the text has to
|
|
come back from the unmasked line or `` `instructions/dev/` `` would
|
|
render as blank space instead of its real content."""
|
|
body = "# Title\n\n## `instructions/dev/`\n"
|
|
headings = toc.iter_headings(body)
|
|
assert headings == [toc.Heading(level=2, text="`instructions/dev/`")]
|
|
|
|
|
|
def test_render_produces_nested_marker_region():
|
|
headings = [
|
|
toc.Heading(level=2, text="First"),
|
|
toc.Heading(level=3, text="Nested"),
|
|
toc.Heading(level=2, text="Second"),
|
|
]
|
|
region = toc.render(headings)
|
|
assert region.startswith(blocks.open_marker(toc.REGION_NAME))
|
|
assert region.endswith(blocks.close_marker(toc.REGION_NAME))
|
|
assert f"## {toc.HEADING_TEXT}" in region
|
|
assert "- [First](#first)" in region
|
|
assert " - [Nested](#nested)" in region
|
|
assert "- [Second](#second)" in region
|
|
|
|
|
|
def test_render_deduplicates_repeated_slugs():
|
|
headings = [toc.Heading(level=2, text="Steps"), toc.Heading(level=2, text="Steps")]
|
|
region = toc.render(headings)
|
|
assert "(#steps)" in region
|
|
assert "(#steps-1)" in region
|
|
|
|
|
|
def test_render_is_empty_for_no_headings():
|
|
assert toc.render([]) == ""
|
|
|
|
|
|
def test_upsert_inserts_before_the_first_level2_heading():
|
|
body = _body(toc.THRESHOLD + 20, 4)
|
|
result = toc.upsert(body)
|
|
assert result != body
|
|
marker_pos = result.index(blocks.open_marker(toc.REGION_NAME))
|
|
first_heading_pos = result.index("## Section 0")
|
|
assert marker_pos < first_heading_pos
|
|
# Title and intro paragraph still precede the region.
|
|
assert result.index("# Title") < marker_pos
|
|
assert result.index("Intro paragraph.") < marker_pos
|
|
|
|
|
|
def test_upsert_is_idempotent():
|
|
body = _body(toc.THRESHOLD + 30, 5)
|
|
once = toc.upsert(body)
|
|
twice = toc.upsert(once)
|
|
assert once == twice
|
|
|
|
|
|
def test_upsert_is_idempotent_without_a_trailing_newline():
|
|
"""A file missing its final newline (tools/CONTRACT.md and
|
|
types/type-spec.md both did, in the real repo) must not make the
|
|
"no region yet" and "region already exists" paths disagree on
|
|
round two."""
|
|
body = _body(toc.THRESHOLD + 20, 4).rstrip("\n")
|
|
once = toc.upsert(body)
|
|
twice = toc.upsert(once)
|
|
assert once == twice
|
|
|
|
|
|
def test_upsert_refreshes_a_stale_region_in_place():
|
|
body = _body(toc.THRESHOLD + 20, 3)
|
|
stale = toc.upsert(body)
|
|
# Rename a heading without touching the (now stale) TOC region.
|
|
changed = stale.replace("## Section 1", "## Renamed Section", 1)
|
|
refreshed = toc.upsert(changed)
|
|
assert "[Renamed Section](#renamed-section)" in refreshed
|
|
assert "[Section 1]" not in refreshed
|
|
|
|
|
|
def test_upsert_removes_the_region_once_the_file_shrinks():
|
|
body = _body(toc.THRESHOLD + 20, 3)
|
|
with_region = toc.upsert(body)
|
|
assert blocks.find(with_region, toc.REGION_NAME) is not None
|
|
|
|
shrunk = "# Title\n\nIntro paragraph.\n\n## Only Section\n\nOne line.\n"
|
|
result = toc.upsert(shrunk)
|
|
assert blocks.find(result, toc.REGION_NAME) is None
|
|
|
|
|
|
def test_stale_regions_is_false_right_after_upsert():
|
|
body = _body(toc.THRESHOLD + 20, 3)
|
|
once = toc.upsert(body)
|
|
assert toc.stale_regions(once) is False
|
|
|
|
|
|
def test_target_files_matches_the_documented_scope():
|
|
"""Integration check against the real repo: every agent-loaded category
|
|
AGENTS.md § File naming names - AGENTS.md itself, every stage contract,
|
|
kb/CONVENTIONS.md, every COLLECTION.md, the flat `instructions/**.md` form,
|
|
every type-spec, every `docs/` page - with `SKILL.md` the one exception."""
|
|
from chemenu import config
|
|
|
|
files = toc.target_files()
|
|
relatives = {f.relative_to(config.ROOT).as_posix() for f in files}
|
|
|
|
assert "AGENTS.md" in relatives
|
|
assert "kb/CONTRACT.md" in relatives
|
|
assert "kb/CONVENTIONS.md" in relatives
|
|
assert "types/type-spec.md" in relatives
|
|
assert "instructions/CONTRACT.md" in relatives
|
|
assert "instructions/dev/version-parts.md" in relatives # flat, still instructions/**.md
|
|
assert "types/source.md" in relatives # a page type-spec, not only the stage contract
|
|
assert "docs/version-model.md" in relatives
|
|
assert not any(rel.endswith("SKILL.md") for rel in relatives)
|
|
|
|
|
|
def test_target_files_lists_type_spec_once_despite_two_sources():
|
|
"""`types/type-spec.md` is both a stage contract and a `types/*.md` file;
|
|
the set in `target_files` is what keeps that from being a special case."""
|
|
from chemenu import config
|
|
|
|
relatives = [f.relative_to(config.ROOT).as_posix() for f in toc.target_files()]
|
|
assert relatives.count("types/type-spec.md") == 1
|
|
|
|
|
|
def test_strip_region_removes_what_types_describe_would_otherwise_echo():
|
|
body = (
|
|
"# A type\n\n"
|
|
f"{blocks.open_marker(toc.REGION_NAME)}\n## Contents\n\n- [X](#x)\n"
|
|
f"{blocks.close_marker(toc.REGION_NAME)}\n\n## X\n\nProse.\n"
|
|
)
|
|
stripped = toc.strip_region(body)
|
|
assert toc.REGION_NAME not in stripped
|
|
assert "## Contents" not in stripped
|
|
assert "## X" in stripped and "Prose." in stripped
|
|
# The heading keeps the blank line the region used to sit in, rather than
|
|
# being welded to the line above it.
|
|
assert "# A type\n\n## X" in stripped
|
|
|
|
|
|
def test_strip_region_leaves_a_body_that_never_had_one_alone():
|
|
body = "# A type\n\n## X\n\nProse.\n"
|
|
assert toc.strip_region(body) == body
|