julee 0.1.5__py3-none-any.whl → 0.1.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. julee/docs/sphinx_hcd/__init__.py +146 -13
  2. julee/docs/sphinx_hcd/domain/__init__.py +5 -0
  3. julee/docs/sphinx_hcd/domain/models/__init__.py +32 -0
  4. julee/docs/sphinx_hcd/domain/models/accelerator.py +152 -0
  5. julee/docs/sphinx_hcd/domain/models/app.py +151 -0
  6. julee/docs/sphinx_hcd/domain/models/code_info.py +121 -0
  7. julee/docs/sphinx_hcd/domain/models/epic.py +79 -0
  8. julee/docs/sphinx_hcd/domain/models/integration.py +230 -0
  9. julee/docs/sphinx_hcd/domain/models/journey.py +222 -0
  10. julee/docs/sphinx_hcd/domain/models/persona.py +106 -0
  11. julee/docs/sphinx_hcd/domain/models/story.py +128 -0
  12. julee/docs/sphinx_hcd/domain/repositories/__init__.py +25 -0
  13. julee/docs/sphinx_hcd/domain/repositories/accelerator.py +98 -0
  14. julee/docs/sphinx_hcd/domain/repositories/app.py +57 -0
  15. julee/docs/sphinx_hcd/domain/repositories/base.py +89 -0
  16. julee/docs/sphinx_hcd/domain/repositories/code_info.py +69 -0
  17. julee/docs/sphinx_hcd/domain/repositories/epic.py +62 -0
  18. julee/docs/sphinx_hcd/domain/repositories/integration.py +79 -0
  19. julee/docs/sphinx_hcd/domain/repositories/journey.py +106 -0
  20. julee/docs/sphinx_hcd/domain/repositories/story.py +68 -0
  21. julee/docs/sphinx_hcd/domain/use_cases/__init__.py +64 -0
  22. julee/docs/sphinx_hcd/domain/use_cases/derive_personas.py +166 -0
  23. julee/docs/sphinx_hcd/domain/use_cases/resolve_accelerator_references.py +236 -0
  24. julee/docs/sphinx_hcd/domain/use_cases/resolve_app_references.py +144 -0
  25. julee/docs/sphinx_hcd/domain/use_cases/resolve_story_references.py +121 -0
  26. julee/docs/sphinx_hcd/parsers/__init__.py +48 -0
  27. julee/docs/sphinx_hcd/parsers/ast.py +150 -0
  28. julee/docs/sphinx_hcd/parsers/gherkin.py +155 -0
  29. julee/docs/sphinx_hcd/parsers/yaml.py +184 -0
  30. julee/docs/sphinx_hcd/repositories/__init__.py +4 -0
  31. julee/docs/sphinx_hcd/repositories/memory/__init__.py +25 -0
  32. julee/docs/sphinx_hcd/repositories/memory/accelerator.py +86 -0
  33. julee/docs/sphinx_hcd/repositories/memory/app.py +45 -0
  34. julee/docs/sphinx_hcd/repositories/memory/base.py +106 -0
  35. julee/docs/sphinx_hcd/repositories/memory/code_info.py +59 -0
  36. julee/docs/sphinx_hcd/repositories/memory/epic.py +54 -0
  37. julee/docs/sphinx_hcd/repositories/memory/integration.py +70 -0
  38. julee/docs/sphinx_hcd/repositories/memory/journey.py +96 -0
  39. julee/docs/sphinx_hcd/repositories/memory/story.py +63 -0
  40. julee/docs/sphinx_hcd/sphinx/__init__.py +28 -0
  41. julee/docs/sphinx_hcd/sphinx/adapters.py +116 -0
  42. julee/docs/sphinx_hcd/sphinx/context.py +163 -0
  43. julee/docs/sphinx_hcd/sphinx/directives/__init__.py +160 -0
  44. julee/docs/sphinx_hcd/sphinx/directives/accelerator.py +576 -0
  45. julee/docs/sphinx_hcd/sphinx/directives/app.py +349 -0
  46. julee/docs/sphinx_hcd/sphinx/directives/base.py +211 -0
  47. julee/docs/sphinx_hcd/sphinx/directives/epic.py +434 -0
  48. julee/docs/sphinx_hcd/sphinx/directives/integration.py +220 -0
  49. julee/docs/sphinx_hcd/sphinx/directives/journey.py +642 -0
  50. julee/docs/sphinx_hcd/sphinx/directives/persona.py +345 -0
  51. julee/docs/sphinx_hcd/sphinx/directives/story.py +575 -0
  52. julee/docs/sphinx_hcd/sphinx/event_handlers/__init__.py +16 -0
  53. julee/docs/sphinx_hcd/sphinx/event_handlers/builder_inited.py +31 -0
  54. julee/docs/sphinx_hcd/sphinx/event_handlers/doctree_read.py +27 -0
  55. julee/docs/sphinx_hcd/sphinx/event_handlers/doctree_resolved.py +43 -0
  56. julee/docs/sphinx_hcd/sphinx/event_handlers/env_purge_doc.py +42 -0
  57. julee/docs/sphinx_hcd/sphinx/initialization.py +139 -0
  58. julee/docs/sphinx_hcd/tests/__init__.py +9 -0
  59. julee/docs/sphinx_hcd/tests/conftest.py +6 -0
  60. julee/docs/sphinx_hcd/tests/domain/__init__.py +1 -0
  61. julee/docs/sphinx_hcd/tests/domain/models/__init__.py +1 -0
  62. julee/docs/sphinx_hcd/tests/domain/models/test_accelerator.py +266 -0
  63. julee/docs/sphinx_hcd/tests/domain/models/test_app.py +258 -0
  64. julee/docs/sphinx_hcd/tests/domain/models/test_code_info.py +231 -0
  65. julee/docs/sphinx_hcd/tests/domain/models/test_epic.py +163 -0
  66. julee/docs/sphinx_hcd/tests/domain/models/test_integration.py +327 -0
  67. julee/docs/sphinx_hcd/tests/domain/models/test_journey.py +249 -0
  68. julee/docs/sphinx_hcd/tests/domain/models/test_persona.py +172 -0
  69. julee/docs/sphinx_hcd/tests/domain/models/test_story.py +216 -0
  70. julee/docs/sphinx_hcd/tests/domain/use_cases/__init__.py +1 -0
  71. julee/docs/sphinx_hcd/tests/domain/use_cases/test_derive_personas.py +314 -0
  72. julee/docs/sphinx_hcd/tests/domain/use_cases/test_resolve_accelerator_references.py +476 -0
  73. julee/docs/sphinx_hcd/tests/domain/use_cases/test_resolve_app_references.py +265 -0
  74. julee/docs/sphinx_hcd/tests/domain/use_cases/test_resolve_story_references.py +229 -0
  75. julee/docs/sphinx_hcd/tests/integration/__init__.py +1 -0
  76. julee/docs/sphinx_hcd/tests/parsers/__init__.py +1 -0
  77. julee/docs/sphinx_hcd/tests/parsers/test_ast.py +298 -0
  78. julee/docs/sphinx_hcd/tests/parsers/test_gherkin.py +282 -0
  79. julee/docs/sphinx_hcd/tests/parsers/test_yaml.py +496 -0
  80. julee/docs/sphinx_hcd/tests/repositories/__init__.py +1 -0
  81. julee/docs/sphinx_hcd/tests/repositories/test_accelerator.py +298 -0
  82. julee/docs/sphinx_hcd/tests/repositories/test_app.py +218 -0
  83. julee/docs/sphinx_hcd/tests/repositories/test_base.py +151 -0
  84. julee/docs/sphinx_hcd/tests/repositories/test_code_info.py +253 -0
  85. julee/docs/sphinx_hcd/tests/repositories/test_epic.py +237 -0
  86. julee/docs/sphinx_hcd/tests/repositories/test_integration.py +268 -0
  87. julee/docs/sphinx_hcd/tests/repositories/test_journey.py +294 -0
  88. julee/docs/sphinx_hcd/tests/repositories/test_story.py +236 -0
  89. julee/docs/sphinx_hcd/tests/sphinx/__init__.py +1 -0
  90. julee/docs/sphinx_hcd/tests/sphinx/directives/__init__.py +1 -0
  91. julee/docs/sphinx_hcd/tests/sphinx/directives/test_base.py +160 -0
  92. julee/docs/sphinx_hcd/tests/sphinx/test_adapters.py +176 -0
  93. julee/docs/sphinx_hcd/tests/sphinx/test_context.py +257 -0
  94. {julee-0.1.5.dist-info → julee-0.1.6.dist-info}/METADATA +2 -1
  95. {julee-0.1.5.dist-info → julee-0.1.6.dist-info}/RECORD +98 -13
  96. julee/docs/sphinx_hcd/accelerators.py +0 -1175
  97. julee/docs/sphinx_hcd/apps.py +0 -518
  98. julee/docs/sphinx_hcd/epics.py +0 -453
  99. julee/docs/sphinx_hcd/integrations.py +0 -310
  100. julee/docs/sphinx_hcd/journeys.py +0 -797
  101. julee/docs/sphinx_hcd/personas.py +0 -457
  102. julee/docs/sphinx_hcd/stories.py +0 -960
  103. {julee-0.1.5.dist-info → julee-0.1.6.dist-info}/WHEEL +0 -0
  104. {julee-0.1.5.dist-info → julee-0.1.6.dist-info}/licenses/LICENSE +0 -0
  105. {julee-0.1.5.dist-info → julee-0.1.6.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,128 @@
1
+ """Story domain model.
2
+
3
+ Represents a user story extracted from a Gherkin .feature file.
4
+ """
5
+
6
+ from pydantic import BaseModel, field_validator
7
+
8
+ from ...utils import normalize_name, slugify
9
+
10
+
11
+ class Story(BaseModel):
12
+ """A user story extracted from a Gherkin feature file.
13
+
14
+ Stories are the primary unit of user-facing functionality in HCD.
15
+ They capture who wants to do what and why.
16
+
17
+ Attributes:
18
+ slug: URL-safe identifier derived from feature title
19
+ feature_title: The Feature: line from the Gherkin file
20
+ persona: The actor from "As a <persona>"
21
+ persona_normalized: Lowercase, spaces-normalized persona for matching
22
+ i_want: The action from "I want to <action>"
23
+ so_that: The benefit from "So that <benefit>"
24
+ app_slug: The application this story belongs to
25
+ app_normalized: Lowercase, spaces-normalized app name for matching
26
+ file_path: Relative path to the .feature file
27
+ abs_path: Absolute path to the .feature file
28
+ gherkin_snippet: The story header portion of the feature file
29
+ """
30
+
31
+ slug: str
32
+ feature_title: str
33
+ persona: str
34
+ persona_normalized: str = ""
35
+ i_want: str = "do something"
36
+ so_that: str = "achieve a goal"
37
+ app_slug: str
38
+ app_normalized: str = ""
39
+ file_path: str
40
+ abs_path: str = ""
41
+ gherkin_snippet: str = ""
42
+
43
+ @field_validator("slug")
44
+ @classmethod
45
+ def validate_slug(cls, v: str) -> str:
46
+ """Ensure slug is not empty."""
47
+ if not v or not v.strip():
48
+ raise ValueError("Story slug cannot be empty")
49
+ return v.strip()
50
+
51
+ @field_validator("feature_title")
52
+ @classmethod
53
+ def validate_feature_title(cls, v: str) -> str:
54
+ """Ensure feature title is not empty."""
55
+ if not v or not v.strip():
56
+ raise ValueError("Feature title cannot be empty")
57
+ return v.strip()
58
+
59
+ @field_validator("persona")
60
+ @classmethod
61
+ def validate_persona(cls, v: str) -> str:
62
+ """Ensure persona is not empty, default to 'unknown'."""
63
+ if not v or not v.strip():
64
+ return "unknown"
65
+ return v.strip()
66
+
67
+ @field_validator("app_slug")
68
+ @classmethod
69
+ def validate_app_slug(cls, v: str) -> str:
70
+ """Ensure app slug is not empty, default to 'unknown'."""
71
+ if not v or not v.strip():
72
+ return "unknown"
73
+ return v.strip()
74
+
75
+ def model_post_init(self, __context) -> None:
76
+ """Compute normalized fields after initialization."""
77
+ if not self.persona_normalized:
78
+ self.persona_normalized = normalize_name(self.persona)
79
+ if not self.app_normalized:
80
+ self.app_normalized = normalize_name(self.app_slug)
81
+
82
+ @classmethod
83
+ def from_feature_file(
84
+ cls,
85
+ feature_title: str,
86
+ persona: str,
87
+ i_want: str,
88
+ so_that: str,
89
+ app_slug: str,
90
+ file_path: str,
91
+ abs_path: str = "",
92
+ gherkin_snippet: str = "",
93
+ ) -> "Story":
94
+ """Create a Story from parsed feature file data.
95
+
96
+ Args:
97
+ feature_title: The Feature: line content
98
+ persona: The "As a" actor
99
+ i_want: The "I want to" action
100
+ so_that: The "So that" benefit
101
+ app_slug: Application slug (from directory structure)
102
+ file_path: Relative path to .feature file
103
+ abs_path: Absolute path to .feature file
104
+ gherkin_snippet: The story header text
105
+
106
+ Returns:
107
+ A new Story instance
108
+ """
109
+ # Include app_slug in slug to avoid collisions between apps
110
+ return cls(
111
+ slug=f"{app_slug}--{slugify(feature_title)}",
112
+ feature_title=feature_title,
113
+ persona=persona,
114
+ i_want=i_want,
115
+ so_that=so_that,
116
+ app_slug=app_slug,
117
+ file_path=file_path,
118
+ abs_path=abs_path,
119
+ gherkin_snippet=gherkin_snippet,
120
+ )
121
+
122
+ def matches_persona(self, persona_name: str) -> bool:
123
+ """Check if this story belongs to a persona (case-insensitive)."""
124
+ return self.persona_normalized == normalize_name(persona_name)
125
+
126
+ def matches_app(self, app_name: str) -> bool:
127
+ """Check if this story belongs to an app (case-insensitive)."""
128
+ return self.app_normalized == normalize_name(app_name)
@@ -0,0 +1,25 @@
1
+ """Repository protocols for sphinx_hcd.
2
+
3
+ Defines async repository interfaces following julee patterns.
4
+ Implementations live in the repositories/ directory.
5
+ """
6
+
7
+ from .accelerator import AcceleratorRepository
8
+ from .app import AppRepository
9
+ from .base import BaseRepository
10
+ from .code_info import CodeInfoRepository
11
+ from .epic import EpicRepository
12
+ from .integration import IntegrationRepository
13
+ from .journey import JourneyRepository
14
+ from .story import StoryRepository
15
+
16
+ __all__ = [
17
+ "AcceleratorRepository",
18
+ "AppRepository",
19
+ "BaseRepository",
20
+ "CodeInfoRepository",
21
+ "EpicRepository",
22
+ "IntegrationRepository",
23
+ "JourneyRepository",
24
+ "StoryRepository",
25
+ ]
@@ -0,0 +1,98 @@
1
+ """AcceleratorRepository protocol.
2
+
3
+ Defines the interface for accelerator data access.
4
+ """
5
+
6
+ from typing import Protocol, runtime_checkable
7
+
8
+ from ..models.accelerator import Accelerator
9
+ from .base import BaseRepository
10
+
11
+
12
+ @runtime_checkable
13
+ class AcceleratorRepository(BaseRepository[Accelerator], Protocol):
14
+ """Repository protocol for Accelerator entities.
15
+
16
+ Extends BaseRepository with accelerator-specific query methods.
17
+ Accelerators are defined in RST documents and support incremental builds
18
+ via docname tracking.
19
+ """
20
+
21
+ async def get_by_status(self, status: str) -> list[Accelerator]:
22
+ """Get all accelerators with a specific status.
23
+
24
+ Args:
25
+ status: Status to filter by (case-insensitive)
26
+
27
+ Returns:
28
+ List of accelerators with matching status
29
+ """
30
+ ...
31
+
32
+ async def get_by_docname(self, docname: str) -> list[Accelerator]:
33
+ """Get all accelerators defined in a specific document.
34
+
35
+ Args:
36
+ docname: RST document name
37
+
38
+ Returns:
39
+ List of accelerators from that document
40
+ """
41
+ ...
42
+
43
+ async def clear_by_docname(self, docname: str) -> int:
44
+ """Remove all accelerators defined in a specific document.
45
+
46
+ Used during incremental builds when a document is re-read.
47
+
48
+ Args:
49
+ docname: RST document name
50
+
51
+ Returns:
52
+ Number of accelerators removed
53
+ """
54
+ ...
55
+
56
+ async def get_by_integration(
57
+ self, integration_slug: str, relationship: str
58
+ ) -> list[Accelerator]:
59
+ """Get accelerators that have a relationship with an integration.
60
+
61
+ Args:
62
+ integration_slug: Integration slug to search for
63
+ relationship: Either "sources_from" or "publishes_to"
64
+
65
+ Returns:
66
+ List of accelerators with this integration relationship
67
+ """
68
+ ...
69
+
70
+ async def get_dependents(self, accelerator_slug: str) -> list[Accelerator]:
71
+ """Get accelerators that depend on a specific accelerator.
72
+
73
+ Args:
74
+ accelerator_slug: Slug of the accelerator to find dependents of
75
+
76
+ Returns:
77
+ List of accelerators that have this accelerator in depends_on
78
+ """
79
+ ...
80
+
81
+ async def get_fed_by(self, accelerator_slug: str) -> list[Accelerator]:
82
+ """Get accelerators that feed into a specific accelerator.
83
+
84
+ Args:
85
+ accelerator_slug: Slug of the accelerator
86
+
87
+ Returns:
88
+ List of accelerators that have this accelerator in feeds_into
89
+ """
90
+ ...
91
+
92
+ async def get_all_statuses(self) -> set[str]:
93
+ """Get all unique statuses across all accelerators.
94
+
95
+ Returns:
96
+ Set of status strings (normalized to lowercase)
97
+ """
98
+ ...
@@ -0,0 +1,57 @@
1
+ """AppRepository protocol.
2
+
3
+ Defines the interface for app data access.
4
+ """
5
+
6
+ from typing import Protocol, runtime_checkable
7
+
8
+ from ..models.app import App, AppType
9
+ from .base import BaseRepository
10
+
11
+
12
+ @runtime_checkable
13
+ class AppRepository(BaseRepository[App], Protocol):
14
+ """Repository protocol for App entities.
15
+
16
+ Extends BaseRepository with app-specific query methods.
17
+ Apps are indexed from YAML manifests and are read-only during
18
+ a Sphinx build (populated at builder-inited, queried during rendering).
19
+ """
20
+
21
+ async def get_by_type(self, app_type: AppType) -> list[App]:
22
+ """Get all apps of a specific type.
23
+
24
+ Args:
25
+ app_type: Application type to filter by
26
+
27
+ Returns:
28
+ List of apps matching the type
29
+ """
30
+ ...
31
+
32
+ async def get_by_name(self, name: str) -> App | None:
33
+ """Get an app by its display name (case-insensitive).
34
+
35
+ Args:
36
+ name: Display name to search for
37
+
38
+ Returns:
39
+ App if found, None otherwise
40
+ """
41
+ ...
42
+
43
+ async def get_all_types(self) -> set[AppType]:
44
+ """Get all unique app types that have apps.
45
+
46
+ Returns:
47
+ Set of app types with at least one app
48
+ """
49
+ ...
50
+
51
+ async def get_apps_with_accelerators(self) -> list[App]:
52
+ """Get all apps that have accelerators defined.
53
+
54
+ Returns:
55
+ List of apps with non-empty accelerators list
56
+ """
57
+ ...
@@ -0,0 +1,89 @@
1
+ """Base repository protocol for sphinx_hcd.
2
+
3
+ Defines the generic repository interface following julee clean architecture
4
+ patterns. All repository operations are async for consistency with julee,
5
+ with sync adapters provided in the sphinx/ application layer.
6
+ """
7
+
8
+ from typing import Protocol, TypeVar, runtime_checkable
9
+
10
+ from pydantic import BaseModel
11
+
12
+ T = TypeVar("T", bound=BaseModel)
13
+
14
+
15
+ @runtime_checkable
16
+ class BaseRepository(Protocol[T]):
17
+ """Generic base repository protocol for HCD entities.
18
+
19
+ This protocol defines the common interface shared by all domain
20
+ repositories in sphinx_hcd. It uses generics to provide type safety
21
+ while eliminating code duplication.
22
+
23
+ Type Parameter:
24
+ T: The domain entity type (must extend Pydantic BaseModel)
25
+
26
+ All methods are async for consistency with julee patterns. The sphinx/
27
+ application layer provides SyncRepositoryAdapter for use in Sphinx
28
+ directives which are synchronous.
29
+ """
30
+
31
+ async def get(self, entity_id: str) -> T | None:
32
+ """Retrieve an entity by ID.
33
+
34
+ Args:
35
+ entity_id: Unique entity identifier (typically a slug)
36
+
37
+ Returns:
38
+ Entity if found, None otherwise
39
+ """
40
+ ...
41
+
42
+ async def get_many(self, entity_ids: list[str]) -> dict[str, T | None]:
43
+ """Retrieve multiple entities by ID.
44
+
45
+ Args:
46
+ entity_ids: List of unique entity identifiers
47
+
48
+ Returns:
49
+ Dict mapping entity_id to entity (or None if not found)
50
+ """
51
+ ...
52
+
53
+ async def save(self, entity: T) -> None:
54
+ """Save an entity.
55
+
56
+ Args:
57
+ entity: Complete entity to save
58
+
59
+ Note:
60
+ Must be idempotent - saving the same entity state is safe.
61
+ """
62
+ ...
63
+
64
+ async def list_all(self) -> list[T]:
65
+ """List all entities.
66
+
67
+ Returns:
68
+ List of all entities in the repository
69
+ """
70
+ ...
71
+
72
+ async def delete(self, entity_id: str) -> bool:
73
+ """Delete an entity by ID.
74
+
75
+ Args:
76
+ entity_id: Unique entity identifier
77
+
78
+ Returns:
79
+ True if entity was deleted, False if not found
80
+ """
81
+ ...
82
+
83
+ async def clear(self) -> None:
84
+ """Remove all entities from the repository.
85
+
86
+ Used primarily for testing and re-initialization during
87
+ Sphinx incremental builds.
88
+ """
89
+ ...
@@ -0,0 +1,69 @@
1
+ """CodeInfoRepository protocol.
2
+
3
+ Defines the interface for bounded context code introspection data access.
4
+ """
5
+
6
+ from typing import Protocol, runtime_checkable
7
+
8
+ from ..models.code_info import BoundedContextInfo
9
+ from .base import BaseRepository
10
+
11
+
12
+ @runtime_checkable
13
+ class CodeInfoRepository(BaseRepository[BoundedContextInfo], Protocol):
14
+ """Repository protocol for BoundedContextInfo entities.
15
+
16
+ Extends BaseRepository with code introspection-specific query methods.
17
+ Code info is populated once at builder-inited by scanning src/ directories.
18
+ """
19
+
20
+ async def get_by_code_dir(self, code_dir: str) -> BoundedContextInfo | None:
21
+ """Get bounded context info by its code directory name.
22
+
23
+ Args:
24
+ code_dir: Directory name in src/ (may differ from slug)
25
+
26
+ Returns:
27
+ BoundedContextInfo if found, None otherwise
28
+ """
29
+ ...
30
+
31
+ async def get_with_entities(self) -> list[BoundedContextInfo]:
32
+ """Get all bounded contexts that have domain entities.
33
+
34
+ Returns:
35
+ List of bounded contexts with at least one entity
36
+ """
37
+ ...
38
+
39
+ async def get_with_use_cases(self) -> list[BoundedContextInfo]:
40
+ """Get all bounded contexts that have use cases.
41
+
42
+ Returns:
43
+ List of bounded contexts with at least one use case
44
+ """
45
+ ...
46
+
47
+ async def get_with_infrastructure(self) -> list[BoundedContextInfo]:
48
+ """Get all bounded contexts that have infrastructure.
49
+
50
+ Returns:
51
+ List of bounded contexts where has_infrastructure is True
52
+ """
53
+ ...
54
+
55
+ async def get_all_entity_names(self) -> set[str]:
56
+ """Get all unique entity class names across all bounded contexts.
57
+
58
+ Returns:
59
+ Set of entity class names
60
+ """
61
+ ...
62
+
63
+ async def get_all_use_case_names(self) -> set[str]:
64
+ """Get all unique use case class names across all bounded contexts.
65
+
66
+ Returns:
67
+ Set of use case class names
68
+ """
69
+ ...
@@ -0,0 +1,62 @@
1
+ """EpicRepository protocol.
2
+
3
+ Defines the interface for epic data access.
4
+ """
5
+
6
+ from typing import Protocol, runtime_checkable
7
+
8
+ from ..models.epic import Epic
9
+ from .base import BaseRepository
10
+
11
+
12
+ @runtime_checkable
13
+ class EpicRepository(BaseRepository[Epic], Protocol):
14
+ """Repository protocol for Epic entities.
15
+
16
+ Extends BaseRepository with epic-specific query methods.
17
+ Epics are defined in RST documents and support incremental builds
18
+ via docname tracking.
19
+ """
20
+
21
+ async def get_by_docname(self, docname: str) -> list[Epic]:
22
+ """Get all epics defined in a specific document.
23
+
24
+ Args:
25
+ docname: RST document name
26
+
27
+ Returns:
28
+ List of epics from that document
29
+ """
30
+ ...
31
+
32
+ async def clear_by_docname(self, docname: str) -> int:
33
+ """Remove all epics defined in a specific document.
34
+
35
+ Used during incremental builds when a document is re-read.
36
+
37
+ Args:
38
+ docname: RST document name
39
+
40
+ Returns:
41
+ Number of epics removed
42
+ """
43
+ ...
44
+
45
+ async def get_with_story_ref(self, story_title: str) -> list[Epic]:
46
+ """Get epics that contain a specific story.
47
+
48
+ Args:
49
+ story_title: Story feature title (case-insensitive)
50
+
51
+ Returns:
52
+ List of epics containing this story in story_refs
53
+ """
54
+ ...
55
+
56
+ async def get_all_story_refs(self) -> set[str]:
57
+ """Get all unique story references across all epics.
58
+
59
+ Returns:
60
+ Set of story titles (normalized)
61
+ """
62
+ ...
@@ -0,0 +1,79 @@
1
+ """IntegrationRepository protocol.
2
+
3
+ Defines the interface for integration data access.
4
+ """
5
+
6
+ from typing import Protocol, runtime_checkable
7
+
8
+ from ..models.integration import Direction, Integration
9
+ from .base import BaseRepository
10
+
11
+
12
+ @runtime_checkable
13
+ class IntegrationRepository(BaseRepository[Integration], Protocol):
14
+ """Repository protocol for Integration entities.
15
+
16
+ Extends BaseRepository with integration-specific query methods.
17
+ Integrations are indexed from YAML manifests and are read-only during
18
+ a Sphinx build (populated at builder-inited, queried during rendering).
19
+ """
20
+
21
+ async def get_by_direction(self, direction: Direction) -> list[Integration]:
22
+ """Get all integrations with a specific data flow direction.
23
+
24
+ Args:
25
+ direction: Direction to filter by
26
+
27
+ Returns:
28
+ List of integrations matching the direction
29
+ """
30
+ ...
31
+
32
+ async def get_by_module(self, module: str) -> Integration | None:
33
+ """Get an integration by its module name.
34
+
35
+ Args:
36
+ module: Python module name (e.g., "pilot_data_collection")
37
+
38
+ Returns:
39
+ Integration if found, None otherwise
40
+ """
41
+ ...
42
+
43
+ async def get_by_name(self, name: str) -> Integration | None:
44
+ """Get an integration by its display name (case-insensitive).
45
+
46
+ Args:
47
+ name: Display name to search for
48
+
49
+ Returns:
50
+ Integration if found, None otherwise
51
+ """
52
+ ...
53
+
54
+ async def get_all_directions(self) -> set[Direction]:
55
+ """Get all unique directions that have integrations.
56
+
57
+ Returns:
58
+ Set of directions with at least one integration
59
+ """
60
+ ...
61
+
62
+ async def get_with_dependencies(self) -> list[Integration]:
63
+ """Get all integrations that have external dependencies.
64
+
65
+ Returns:
66
+ List of integrations with non-empty depends_on list
67
+ """
68
+ ...
69
+
70
+ async def get_by_dependency(self, dep_name: str) -> list[Integration]:
71
+ """Get all integrations that depend on a specific external system.
72
+
73
+ Args:
74
+ dep_name: External dependency name (case-insensitive)
75
+
76
+ Returns:
77
+ List of integrations that have this dependency
78
+ """
79
+ ...