← Files NovelistARCHIVED FILE

skills/analytical-review/tests/test_inventory_review.py

19 KB · Oct 4, 2026 · 12:29 UTC

↓ Download file

"""Regression tests for content-free analytical-review scope inventories."""

from __future__ import annotations

import hashlib
import importlib.util
import sys
import tempfile
import unittest
from pathlib import Path
from unittest import mock


SCRIPT = Path(__file__).resolve().parents[1] / "scripts" / "inventory_review.py"
SPEC = importlib.util.spec_from_file_location("inventory_review_under_test", SCRIPT)
assert SPEC and SPEC.loader
inventory_review = importlib.util.module_from_spec(SPEC)
sys.modules[SPEC.name] = inventory_review
SPEC.loader.exec_module(inventory_review)


def write_source(
    path: Path,
    *,
    source_type: str,
    title: str,
    body: str,
    number: int | None = None,
) -> None:
    number_line = f"number: {number}\n" if number is not None else ""
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(
        f"""---
id: {source_type}-{path.stem}
type: {source_type}
{number_line}title: "{title}"
status: draft
tags: []
created: 2026-01-01
updated: 2026-01-01
---
# {title}

{body}
""",
        encoding="utf-8",
    )


def write_chapter(path: Path, *, number: int, title: str, draft: str) -> None:
    write_source(
        path,
        source_type="chapter",
        title=title,
        number=number,
        body=f"""## Synopsis

Editorial summary.

## Draft

{draft}

## Revision Notes

None.
""",
    )


def file_hash(path: Path) -> str:
    return hashlib.sha256(path.read_bytes()).hexdigest()


class ReviewInventoryTests(unittest.TestCase):
    def test_manuscript_inventory_covers_every_numbered_draft_without_copying_prose(
        self,
    ) -> None:
        with tempfile.TemporaryDirectory() as directory:
            root = Path(directory)
            private_marker = "SENTINEL_PRIVATE_PROSE_MUST_NOT_APPEAR"
            write_chapter(
                root / "chapters" / "001.first-turn.md",
                number=1,
                title="First Turn",
                draft=f"An opening decision. {private_marker}",
            )
            write_chapter(
                root / "chapters" / "002.second-turn.md",
                number=2,
                title="Second Turn",
                draft="A consequence changes the next decision.",
            )
            (root / "chapters" / "_template.md").write_text(
                private_marker,
                encoding="utf-8",
            )

            result = inventory_review.build_inventory(
                root,
                mode="manuscript",
                targets=[],
                chapter_numbers=[],
                max_batch_tokens=20,
            )

            self.assertEqual(result["unit_count"], 2)
            self.assertEqual(result["eligible_unit_count"], 2)
            self.assertTrue(result["coverage_complete"])
            self.assertEqual(
                [unit["path"] for unit in result["units"]],
                ["chapters/001.first-turn.md", "chapters/002.second-turn.md"],
            )
            self.assertTrue(all(unit["review_section"] == "Draft" for unit in result["units"]))
            self.assertTrue(all(unit["line_start"] for unit in result["units"]))
            self.assertGreaterEqual(len(result["batches"]), 2)
            self.assertNotIn(private_marker, str(result))

    def test_outline_mode_inventories_plans_and_never_claims_chapter_draft_scope(
        self,
    ) -> None:
        with tempfile.TemporaryDirectory() as directory:
            root = Path(directory)
            write_source(
                root / "plot" / "000.master-plot.md",
                source_type="plot",
                title="Master Plot",
                body="## Promise\n\nA choice changes the relationship.",
            )
            write_source(
                root / "outlines" / "000.master-outline.md",
                source_type="outline",
                title="Master Outline",
                body="## Sequence\n\nA planned consequence follows.",
            )
            write_chapter(
                root / "chapters" / "001.existing-draft.md",
                number=1,
                title="Existing Draft",
                draft="This chapter is outside outline mode.",
            )

            result = inventory_review.build_inventory(
                root,
                mode="outline",
                targets=[],
                chapter_numbers=[],
                max_batch_tokens=12000,
            )

            self.assertEqual(result["unit_count"], 2)
            self.assertTrue(all(unit["review_section"] == "document" for unit in result["units"]))
            self.assertFalse(any(unit["kind"] == "chapter" for unit in result["units"]))

    def test_outline_mode_rejects_an_explicit_chapter_target(self) -> None:
        with tempfile.TemporaryDirectory() as directory:
            root = Path(directory)
            write_chapter(
                root / "chapters" / "001.existing-draft.md",
                number=1,
                title="Existing Draft",
                draft="This chapter must not become outline evidence.",
            )

            with self.assertRaisesRegex(
                ValueError,
                "outline targets must be direct Markdown files under plot/ or outlines/",
            ):
                inventory_review.build_inventory(
                    root,
                    mode="outline",
                    targets=["chapters/001.existing-draft.md"],
                    chapter_numbers=[],
                    max_batch_tokens=12000,
                )

    def test_chapter_mode_requires_and_honors_an_explicit_scope(self) -> None:
        with tempfile.TemporaryDirectory() as directory:
            root = Path(directory)
            write_chapter(
                root / "chapters" / "001.first.md",
                number=1,
                title="First",
                draft="First draft.",
            )
            write_chapter(
                root / "chapters" / "002.second.md",
                number=2,
                title="Second",
                draft="Second draft.",
            )

            with self.assertRaisesRegex(ValueError, "requires --chapter or --target"):
                inventory_review.build_inventory(
                    root,
                    mode="chapter",
                    targets=[],
                    chapter_numbers=[],
                    max_batch_tokens=12000,
                )

            result = inventory_review.build_inventory(
                root,
                mode="chapter",
                targets=[],
                chapter_numbers=[2],
                max_batch_tokens=12000,
            )
            self.assertEqual([unit["number"] for unit in result["units"]], [2])

    def test_regression_mode_requires_a_bounded_scope(self) -> None:
        with tempfile.TemporaryDirectory() as directory:
            root = Path(directory)
            write_chapter(
                root / "chapters" / "001.changed.md",
                number=1,
                title="Changed",
                draft="A revised draft.",
            )

            with self.assertRaisesRegex(
                ValueError,
                "regression mode requires --chapter or --target",
            ):
                inventory_review.build_inventory(
                    root,
                    mode="regression",
                    targets=[],
                    chapter_numbers=[],
                    max_batch_tokens=12000,
                )

            result = inventory_review.build_inventory(
                root,
                mode="regression",
                targets=[],
                chapter_numbers=[1],
                max_batch_tokens=12000,
            )
            self.assertEqual([unit["number"] for unit in result["units"]], [1])

    def test_chapter_mode_rejects_an_explicit_outline_target(self) -> None:
        with tempfile.TemporaryDirectory() as directory:
            root = Path(directory)
            write_source(
                root / "outlines" / "000.master-outline.md",
                source_type="outline",
                title="Master Outline",
                body="## Sequence\n\nA planned change.",
            )

            with self.assertRaisesRegex(
                ValueError,
                "chapter targets must be numbered Markdown files under chapters/",
            ):
                inventory_review.build_inventory(
                    root,
                    mode="chapter",
                    targets=["outlines/000.master-outline.md"],
                    chapter_numbers=[],
                    max_batch_tokens=12000,
                )

    def test_malformed_draft_is_counted_but_not_marked_covered(self) -> None:
        with tempfile.TemporaryDirectory() as directory:
            root = Path(directory)
            write_source(
                root / "chapters" / "001.missing-draft.md",
                source_type="chapter",
                title="Missing Draft",
                number=1,
                body="## Synopsis\n\nOnly editorial material.\n\n## Revision Notes\n\nNone.",
            )

            result = inventory_review.build_inventory(
                root,
                mode="manuscript",
                targets=[],
                chapter_numbers=[],
                max_batch_tokens=12000,
            )

            self.assertEqual(result["unit_count"], 1)
            self.assertEqual(result["eligible_unit_count"], 0)
            self.assertFalse(result["coverage_complete"])
            self.assertIn("expected one case-sensitive Draft section", str(result["issues"]))
            self.assertEqual(result["batches"], [])

    def test_numbering_gaps_remain_reviewable_and_oversized_units_are_visible(self) -> None:
        with tempfile.TemporaryDirectory() as directory:
            root = Path(directory)
            write_chapter(
                root / "chapters" / "001.first.md",
                number=1,
                title="First",
                draft="Short draft.",
            )
            write_chapter(
                root / "chapters" / "003.third.md",
                number=3,
                title="Third",
                draft="A" * 200,
            )

            result = inventory_review.build_inventory(
                root,
                mode="manuscript",
                targets=[],
                chapter_numbers=[],
                max_batch_tokens=20,
            )

            self.assertTrue(result["coverage_complete"])
            self.assertEqual([unit["number"] for unit in result["units"]], [1, 3])
            self.assertTrue(any(batch["oversized"] for batch in result["batches"]))

    def test_duplicate_chapter_numbers_block_complete_coverage(self) -> None:
        with tempfile.TemporaryDirectory() as directory:
            root = Path(directory)
            write_chapter(
                root / "chapters" / "001.first.md",
                number=1,
                title="First",
                draft="First version.",
            )
            write_chapter(
                root / "chapters" / "001.alternate.md",
                number=1,
                title="Alternate",
                draft="Alternate version.",
            )

            result = inventory_review.build_inventory(
                root,
                mode="manuscript",
                targets=[],
                chapter_numbers=[],
                max_batch_tokens=12000,
            )

            self.assertFalse(result["coverage_complete"])
            self.assertEqual(result["eligible_unit_count"], 0)
            self.assertIn("duplicate chapter number", str(result["issues"]))

    def test_inventory_is_read_only(self) -> None:
        with tempfile.TemporaryDirectory() as directory:
            root = Path(directory)
            chapter = root / "chapters" / "001.read-only.md"
            write_chapter(
                chapter,
                number=1,
                title="Read Only",
                draft="The source must remain unchanged.",
            )
            before = file_hash(chapter)

            inventory_review.build_inventory(
                root,
                mode="manuscript",
                targets=[],
                chapter_numbers=[],
                max_batch_tokens=12000,
            )

            self.assertEqual(file_hash(chapter), before)

    def test_target_must_remain_inside_the_project(self) -> None:
        with tempfile.TemporaryDirectory() as directory:
            root = Path(directory) / "project"
            root.mkdir()
            outside = Path(directory) / "outside.md"
            outside.write_text("# Outside\n", encoding="utf-8")

            with self.assertRaisesRegex(ValueError, "escapes the project root"):
                inventory_review.build_inventory(
                    root,
                    mode="outline",
                    targets=["../outside.md"],
                    chapter_numbers=[],
                    max_batch_tokens=12000,
                )

    def test_target_symbolic_links_are_rejected(self) -> None:
        with tempfile.TemporaryDirectory() as directory:
            root = Path(directory)
            source = root / "outlines" / "source.md"
            write_source(
                source,
                source_type="outline",
                title="Source",
                body="## Sequence\n\nA planned change.",
            )
            link = root / "outlines" / "linked.md"
            link.symlink_to(source)

            with self.assertRaisesRegex(ValueError, "must not be a symbolic link"):
                inventory_review.build_inventory(
                    root,
                    mode="outline",
                    targets=["outlines/linked.md"],
                    chapter_numbers=[],
                    max_batch_tokens=12000,
                )

    def test_discovery_does_not_follow_a_symbolic_linked_source_directory(self) -> None:
        with tempfile.TemporaryDirectory() as directory:
            root = Path(directory) / "project"
            outside = Path(directory) / "outside"
            root.mkdir()
            write_chapter(
                outside / "001.external.md",
                number=1,
                title="External",
                draft="This external source must not be inventoried.",
            )
            (root / "chapters").symlink_to(outside)

            with self.assertRaisesRegex(
                ValueError,
                "source directory must not be a symbolic link: chapters",
            ):
                inventory_review.build_inventory(
                    root,
                    mode="manuscript",
                    targets=[],
                    chapter_numbers=[],
                    max_batch_tokens=12000,
                )

    def test_discovery_blocks_a_symbolic_linked_chapter_without_hiding_it(self) -> None:
        with tempfile.TemporaryDirectory() as directory:
            root = Path(directory) / "project"
            outside = Path(directory) / "outside"
            write_chapter(
                root / "chapters" / "001.local.md",
                number=1,
                title="Local",
                draft="The local source is reviewable.",
            )
            write_chapter(
                outside / "002.external.md",
                number=2,
                title="External",
                draft="This external source must block complete coverage.",
            )
            (root / "chapters" / "002.external.md").symlink_to(
                outside / "002.external.md"
            )

            with self.assertRaisesRegex(
                ValueError,
                "discovered source must not be a symbolic link",
            ):
                inventory_review.build_inventory(
                    root,
                    mode="manuscript",
                    targets=[],
                    chapter_numbers=[],
                    max_batch_tokens=12000,
                )

    def test_cli_refuses_to_persist_inventory_inside_the_reviewed_project(self) -> None:
        with tempfile.TemporaryDirectory() as directory:
            root = Path(directory)
            write_source(
                root / "outlines" / "000.master-outline.md",
                source_type="outline",
                title="Master Outline",
                body="## Sequence\n\nA planned change.",
            )
            with mock.patch.object(
                sys,
                "argv",
                [
                    str(SCRIPT),
                    "--project-root",
                    str(root),
                    "--mode",
                    "outline",
                    "--output",
                    str(root / "review-inventory.json"),
                ],
            ):
                with self.assertRaisesRegex(
                    SystemExit,
                    "--output must remain outside the reviewed project",
                ):
                    inventory_review.main()

    def test_cli_refuses_an_existing_or_symbolic_linked_output(self) -> None:
        with tempfile.TemporaryDirectory() as directory:
            temporary_root = Path(directory)
            root = temporary_root / "project"
            write_source(
                root / "outlines" / "000.master-outline.md",
                source_type="outline",
                title="Master Outline",
                body="## Sequence\n\nA planned change.",
            )
            protected = temporary_root / "protected.txt"
            protected.write_text("do not overwrite", encoding="utf-8")
            output = temporary_root / "inventory.json"
            output.symlink_to(protected)

            with mock.patch.object(
                sys,
                "argv",
                [
                    str(SCRIPT),
                    "--project-root",
                    str(root),
                    "--mode",
                    "outline",
                    "--output",
                    str(output),
                ],
            ):
                with self.assertRaisesRegex(
                    SystemExit,
                    "--output must not already exist",
                ):
                    inventory_review.main()

            self.assertEqual(protected.read_text(encoding="utf-8"), "do not overwrite")

    def test_cli_creates_a_fresh_output_with_private_permissions(self) -> None:
        with tempfile.TemporaryDirectory() as directory:
            temporary_root = Path(directory)
            root = temporary_root / "project"
            write_source(
                root / "outlines" / "000.master-outline.md",
                source_type="outline",
                title="Master Outline",
                body="## Sequence\n\nA planned change.",
            )
            output = temporary_root / "inventory.json"

            with mock.patch.object(
                sys,
                "argv",
                [
                    str(SCRIPT),
                    "--project-root",
                    str(root),
                    "--mode",
                    "outline",
                    "--output",
                    str(output),
                ],
            ):
                inventory_review.main()

            self.assertTrue(output.is_file())
            self.assertEqual(output.stat().st_mode & 0o777, 0o600)


if __name__ == "__main__":
    unittest.main()

SHA-256: 5e496c5bce785192a0066c20a427c4aaa74c79594237ad598d1f2aa40f35724e