1"""Package data registry — declarative manifest of runtime-read assets.

2

3 Every file the little_loops package reads at runtime must appear in

4 PACKAGE_DATA_ASSETS. The completeness check (test_package_data_manifest.py)

5 asserts each entry is accessible via importlib.resources.files("little_loops"),

6 so adding a new asset read without registering it here will not be caught —

7 but registering it and omitting it from the package source will fail the test.

8

9 Usage in the completeness check::

10

11 from little_loops.package_data import PACKAGE_DATA_ASSETS, list_missing_assets

12

13 missing = list_missing_assets()

14 assert not missing, f"Assets not accessible: {missing}"

15"""

16

17 from __future__ import annotations

18

19 import importlib.resources

20 from typing import Final

21

22_PACKAGE: Final[str] = "little_loops"

23

24# Declarative manifest: each entry is a tuple of path parts relative to

25# the little_loops package root. Add an entry here whenever new package

26# data is referenced at runtime. Omitting an entry gives a false-green

27# completeness result — the check won't catch a missing asset it doesn't know about.

28 PACKAGE_DATA_ASSETS: Final[tuple[tuple[str, ...], ...]] = (

29 ("assets", "ll-cli-logo.txt"),

30 ("hooks", "prompts", "optimize-prompt-hook.md"),

31 ("hooks", "adapters", "codex", "hooks.json"),

32 ("templates", "bug-sections.json"),

33 ("templates", "enh-sections.json"),

34 ("templates", "feat-sections.json"),

35 ("templates", "epic-sections.json"),

36 ("templates", "ll-goals-template.md"),

37 ("templates", "generic.json"),

38 ("templates", "python-generic.json"),

39 ("templates", "javascript.json"),

40 ("templates", "typescript.json"),

41 ("templates", "rust.json"),

42 ("templates", "go.json"),

43 ("templates", "java-maven.json"),

44 ("templates", "java-gradle.json"),

45 ("templates", "dotnet.json"),

46)

47

48

49 def check_asset_accessible(parts: tuple[str, ...]) -> bool:

50 """Return True if the asset is reachable via importlib.resources."""

51 try:

52 traversable = importlib.resources.files(_PACKAGE)

53 for part in parts:

54 traversable = traversable.joinpath(part)

55 return traversable.is_file() # type: ignore[return-value]

56 except Exception:

57 return False

58

59

60 def list_missing_assets() -> list[tuple[str, ...]]:

61 """Return registered assets not accessible in the current installation."""

62 return [parts for parts in PACKAGE_DATA_ASSETS if not check_asset_accessible(parts)]