julee 0.1.4__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 (165) hide show
  1. julee/__init__.py +1 -1
  2. julee/api/tests/routers/test_assembly_specifications.py +2 -0
  3. julee/api/tests/routers/test_documents.py +2 -0
  4. julee/api/tests/routers/test_knowledge_service_configs.py +2 -0
  5. julee/api/tests/routers/test_knowledge_service_queries.py +2 -0
  6. julee/api/tests/routers/test_system.py +2 -0
  7. julee/api/tests/routers/test_workflows.py +2 -0
  8. julee/api/tests/test_app.py +2 -0
  9. julee/api/tests/test_dependencies.py +2 -0
  10. julee/api/tests/test_requests.py +2 -0
  11. julee/contrib/polling/__init__.py +22 -19
  12. julee/contrib/polling/apps/__init__.py +17 -0
  13. julee/contrib/polling/apps/worker/__init__.py +17 -0
  14. julee/contrib/polling/apps/worker/pipelines.py +288 -0
  15. julee/contrib/polling/domain/__init__.py +7 -9
  16. julee/contrib/polling/domain/models/__init__.py +6 -7
  17. julee/contrib/polling/domain/models/polling_config.py +18 -1
  18. julee/contrib/polling/domain/services/__init__.py +6 -5
  19. julee/contrib/polling/domain/services/poller.py +1 -1
  20. julee/contrib/polling/infrastructure/__init__.py +9 -8
  21. julee/contrib/polling/infrastructure/services/__init__.py +6 -5
  22. julee/contrib/polling/infrastructure/services/polling/__init__.py +6 -5
  23. julee/contrib/polling/infrastructure/services/polling/http/__init__.py +6 -5
  24. julee/contrib/polling/infrastructure/services/polling/http/http_poller_service.py +5 -2
  25. julee/contrib/polling/infrastructure/temporal/__init__.py +12 -12
  26. julee/contrib/polling/infrastructure/temporal/activities.py +1 -1
  27. julee/contrib/polling/infrastructure/temporal/manager.py +291 -0
  28. julee/contrib/polling/infrastructure/temporal/proxies.py +1 -1
  29. julee/contrib/polling/tests/unit/apps/worker/test_pipelines.py +580 -0
  30. julee/contrib/polling/tests/unit/infrastructure/services/polling/http/test_http_poller_service.py +40 -2
  31. julee/contrib/polling/tests/unit/infrastructure/temporal/__init__.py +7 -0
  32. julee/contrib/polling/tests/unit/infrastructure/temporal/test_manager.py +475 -0
  33. julee/docs/sphinx_hcd/__init__.py +146 -13
  34. julee/docs/sphinx_hcd/domain/__init__.py +5 -0
  35. julee/docs/sphinx_hcd/domain/models/__init__.py +32 -0
  36. julee/docs/sphinx_hcd/domain/models/accelerator.py +152 -0
  37. julee/docs/sphinx_hcd/domain/models/app.py +151 -0
  38. julee/docs/sphinx_hcd/domain/models/code_info.py +121 -0
  39. julee/docs/sphinx_hcd/domain/models/epic.py +79 -0
  40. julee/docs/sphinx_hcd/domain/models/integration.py +230 -0
  41. julee/docs/sphinx_hcd/domain/models/journey.py +222 -0
  42. julee/docs/sphinx_hcd/domain/models/persona.py +106 -0
  43. julee/docs/sphinx_hcd/domain/models/story.py +128 -0
  44. julee/docs/sphinx_hcd/domain/repositories/__init__.py +25 -0
  45. julee/docs/sphinx_hcd/domain/repositories/accelerator.py +98 -0
  46. julee/docs/sphinx_hcd/domain/repositories/app.py +57 -0
  47. julee/docs/sphinx_hcd/domain/repositories/base.py +89 -0
  48. julee/docs/sphinx_hcd/domain/repositories/code_info.py +69 -0
  49. julee/docs/sphinx_hcd/domain/repositories/epic.py +62 -0
  50. julee/docs/sphinx_hcd/domain/repositories/integration.py +79 -0
  51. julee/docs/sphinx_hcd/domain/repositories/journey.py +106 -0
  52. julee/docs/sphinx_hcd/domain/repositories/story.py +68 -0
  53. julee/docs/sphinx_hcd/domain/use_cases/__init__.py +64 -0
  54. julee/docs/sphinx_hcd/domain/use_cases/derive_personas.py +166 -0
  55. julee/docs/sphinx_hcd/domain/use_cases/resolve_accelerator_references.py +236 -0
  56. julee/docs/sphinx_hcd/domain/use_cases/resolve_app_references.py +144 -0
  57. julee/docs/sphinx_hcd/domain/use_cases/resolve_story_references.py +121 -0
  58. julee/docs/sphinx_hcd/parsers/__init__.py +48 -0
  59. julee/docs/sphinx_hcd/parsers/ast.py +150 -0
  60. julee/docs/sphinx_hcd/parsers/gherkin.py +155 -0
  61. julee/docs/sphinx_hcd/parsers/yaml.py +184 -0
  62. julee/docs/sphinx_hcd/repositories/__init__.py +4 -0
  63. julee/docs/sphinx_hcd/repositories/memory/__init__.py +25 -0
  64. julee/docs/sphinx_hcd/repositories/memory/accelerator.py +86 -0
  65. julee/docs/sphinx_hcd/repositories/memory/app.py +45 -0
  66. julee/docs/sphinx_hcd/repositories/memory/base.py +106 -0
  67. julee/docs/sphinx_hcd/repositories/memory/code_info.py +59 -0
  68. julee/docs/sphinx_hcd/repositories/memory/epic.py +54 -0
  69. julee/docs/sphinx_hcd/repositories/memory/integration.py +70 -0
  70. julee/docs/sphinx_hcd/repositories/memory/journey.py +96 -0
  71. julee/docs/sphinx_hcd/repositories/memory/story.py +63 -0
  72. julee/docs/sphinx_hcd/sphinx/__init__.py +28 -0
  73. julee/docs/sphinx_hcd/sphinx/adapters.py +116 -0
  74. julee/docs/sphinx_hcd/sphinx/context.py +163 -0
  75. julee/docs/sphinx_hcd/sphinx/directives/__init__.py +160 -0
  76. julee/docs/sphinx_hcd/sphinx/directives/accelerator.py +576 -0
  77. julee/docs/sphinx_hcd/sphinx/directives/app.py +349 -0
  78. julee/docs/sphinx_hcd/sphinx/directives/base.py +211 -0
  79. julee/docs/sphinx_hcd/sphinx/directives/epic.py +434 -0
  80. julee/docs/sphinx_hcd/sphinx/directives/integration.py +220 -0
  81. julee/docs/sphinx_hcd/sphinx/directives/journey.py +642 -0
  82. julee/docs/sphinx_hcd/sphinx/directives/persona.py +345 -0
  83. julee/docs/sphinx_hcd/sphinx/directives/story.py +575 -0
  84. julee/docs/sphinx_hcd/sphinx/event_handlers/__init__.py +16 -0
  85. julee/docs/sphinx_hcd/sphinx/event_handlers/builder_inited.py +31 -0
  86. julee/docs/sphinx_hcd/sphinx/event_handlers/doctree_read.py +27 -0
  87. julee/docs/sphinx_hcd/sphinx/event_handlers/doctree_resolved.py +43 -0
  88. julee/docs/sphinx_hcd/sphinx/event_handlers/env_purge_doc.py +42 -0
  89. julee/docs/sphinx_hcd/sphinx/initialization.py +139 -0
  90. julee/docs/sphinx_hcd/tests/__init__.py +9 -0
  91. julee/docs/sphinx_hcd/tests/conftest.py +6 -0
  92. julee/docs/sphinx_hcd/tests/domain/__init__.py +1 -0
  93. julee/docs/sphinx_hcd/tests/domain/models/__init__.py +1 -0
  94. julee/docs/sphinx_hcd/tests/domain/models/test_accelerator.py +266 -0
  95. julee/docs/sphinx_hcd/tests/domain/models/test_app.py +258 -0
  96. julee/docs/sphinx_hcd/tests/domain/models/test_code_info.py +231 -0
  97. julee/docs/sphinx_hcd/tests/domain/models/test_epic.py +163 -0
  98. julee/docs/sphinx_hcd/tests/domain/models/test_integration.py +327 -0
  99. julee/docs/sphinx_hcd/tests/domain/models/test_journey.py +249 -0
  100. julee/docs/sphinx_hcd/tests/domain/models/test_persona.py +172 -0
  101. julee/docs/sphinx_hcd/tests/domain/models/test_story.py +216 -0
  102. julee/docs/sphinx_hcd/tests/domain/use_cases/__init__.py +1 -0
  103. julee/docs/sphinx_hcd/tests/domain/use_cases/test_derive_personas.py +314 -0
  104. julee/docs/sphinx_hcd/tests/domain/use_cases/test_resolve_accelerator_references.py +476 -0
  105. julee/docs/sphinx_hcd/tests/domain/use_cases/test_resolve_app_references.py +265 -0
  106. julee/docs/sphinx_hcd/tests/domain/use_cases/test_resolve_story_references.py +229 -0
  107. julee/docs/sphinx_hcd/tests/integration/__init__.py +1 -0
  108. julee/docs/sphinx_hcd/tests/parsers/__init__.py +1 -0
  109. julee/docs/sphinx_hcd/tests/parsers/test_ast.py +298 -0
  110. julee/docs/sphinx_hcd/tests/parsers/test_gherkin.py +282 -0
  111. julee/docs/sphinx_hcd/tests/parsers/test_yaml.py +496 -0
  112. julee/docs/sphinx_hcd/tests/repositories/__init__.py +1 -0
  113. julee/docs/sphinx_hcd/tests/repositories/test_accelerator.py +298 -0
  114. julee/docs/sphinx_hcd/tests/repositories/test_app.py +218 -0
  115. julee/docs/sphinx_hcd/tests/repositories/test_base.py +151 -0
  116. julee/docs/sphinx_hcd/tests/repositories/test_code_info.py +253 -0
  117. julee/docs/sphinx_hcd/tests/repositories/test_epic.py +237 -0
  118. julee/docs/sphinx_hcd/tests/repositories/test_integration.py +268 -0
  119. julee/docs/sphinx_hcd/tests/repositories/test_journey.py +294 -0
  120. julee/docs/sphinx_hcd/tests/repositories/test_story.py +236 -0
  121. julee/docs/sphinx_hcd/tests/sphinx/__init__.py +1 -0
  122. julee/docs/sphinx_hcd/tests/sphinx/directives/__init__.py +1 -0
  123. julee/docs/sphinx_hcd/tests/sphinx/directives/test_base.py +160 -0
  124. julee/docs/sphinx_hcd/tests/sphinx/test_adapters.py +176 -0
  125. julee/docs/sphinx_hcd/tests/sphinx/test_context.py +257 -0
  126. julee/domain/models/assembly/tests/test_assembly.py +2 -0
  127. julee/domain/models/assembly_specification/tests/test_assembly_specification.py +2 -0
  128. julee/domain/models/assembly_specification/tests/test_knowledge_service_query.py +2 -0
  129. julee/domain/models/custom_fields/tests/test_custom_fields.py +2 -0
  130. julee/domain/models/document/tests/test_document.py +2 -0
  131. julee/domain/models/policy/tests/test_document_policy_validation.py +2 -0
  132. julee/domain/models/policy/tests/test_policy.py +2 -0
  133. julee/domain/use_cases/tests/test_extract_assemble_data.py +2 -0
  134. julee/domain/use_cases/tests/test_initialize_system_data.py +2 -0
  135. julee/domain/use_cases/tests/test_validate_document.py +2 -0
  136. julee/maintenance/release.py +10 -5
  137. julee/repositories/memory/tests/test_document.py +2 -0
  138. julee/repositories/memory/tests/test_document_policy_validation.py +2 -0
  139. julee/repositories/memory/tests/test_policy.py +2 -0
  140. julee/repositories/minio/tests/test_assembly.py +2 -0
  141. julee/repositories/minio/tests/test_assembly_specification.py +2 -0
  142. julee/repositories/minio/tests/test_client_protocol.py +3 -0
  143. julee/repositories/minio/tests/test_document.py +2 -0
  144. julee/repositories/minio/tests/test_document_policy_validation.py +2 -0
  145. julee/repositories/minio/tests/test_knowledge_service_config.py +2 -0
  146. julee/repositories/minio/tests/test_knowledge_service_query.py +2 -0
  147. julee/repositories/minio/tests/test_policy.py +2 -0
  148. julee/services/knowledge_service/anthropic/tests/test_knowledge_service.py +2 -0
  149. julee/services/knowledge_service/memory/test_knowledge_service.py +2 -0
  150. julee/services/knowledge_service/test_factory.py +2 -0
  151. julee/util/tests/test_decorators.py +2 -0
  152. julee-0.1.6.dist-info/METADATA +104 -0
  153. julee-0.1.6.dist-info/RECORD +288 -0
  154. julee/docs/sphinx_hcd/accelerators.py +0 -1175
  155. julee/docs/sphinx_hcd/apps.py +0 -518
  156. julee/docs/sphinx_hcd/epics.py +0 -453
  157. julee/docs/sphinx_hcd/integrations.py +0 -310
  158. julee/docs/sphinx_hcd/journeys.py +0 -797
  159. julee/docs/sphinx_hcd/personas.py +0 -457
  160. julee/docs/sphinx_hcd/stories.py +0 -960
  161. julee-0.1.4.dist-info/METADATA +0 -197
  162. julee-0.1.4.dist-info/RECORD +0 -196
  163. {julee-0.1.4.dist-info → julee-0.1.6.dist-info}/WHEEL +0 -0
  164. {julee-0.1.4.dist-info → julee-0.1.6.dist-info}/licenses/LICENSE +0 -0
  165. {julee-0.1.4.dist-info → julee-0.1.6.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,86 @@
1
+ """Memory implementation of AcceleratorRepository."""
2
+
3
+ import logging
4
+
5
+ from ...domain.models.accelerator import Accelerator
6
+ from ...domain.repositories.accelerator import AcceleratorRepository
7
+ from .base import MemoryRepositoryMixin
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ class MemoryAcceleratorRepository(
13
+ MemoryRepositoryMixin[Accelerator], AcceleratorRepository
14
+ ):
15
+ """In-memory implementation of AcceleratorRepository.
16
+
17
+ Accelerators are stored in a dictionary keyed by slug. This implementation
18
+ is used during Sphinx builds where accelerators are populated during doctree
19
+ processing and support incremental builds via docname tracking.
20
+ """
21
+
22
+ def __init__(self) -> None:
23
+ """Initialize with empty storage."""
24
+ self.storage: dict[str, Accelerator] = {}
25
+ self.entity_name = "Accelerator"
26
+ self.id_field = "slug"
27
+
28
+ async def get_by_status(self, status: str) -> list[Accelerator]:
29
+ """Get all accelerators with a specific status."""
30
+ status_normalized = status.lower().strip()
31
+ return [
32
+ accel
33
+ for accel in self.storage.values()
34
+ if accel.status_normalized == status_normalized
35
+ ]
36
+
37
+ async def get_by_docname(self, docname: str) -> list[Accelerator]:
38
+ """Get all accelerators defined in a specific document."""
39
+ return [accel for accel in self.storage.values() if accel.docname == docname]
40
+
41
+ async def clear_by_docname(self, docname: str) -> int:
42
+ """Remove all accelerators defined in a specific document."""
43
+ to_remove = [
44
+ slug for slug, accel in self.storage.items() if accel.docname == docname
45
+ ]
46
+ for slug in to_remove:
47
+ del self.storage[slug]
48
+ return len(to_remove)
49
+
50
+ async def get_by_integration(
51
+ self, integration_slug: str, relationship: str
52
+ ) -> list[Accelerator]:
53
+ """Get accelerators that have a relationship with an integration."""
54
+ result = []
55
+ for accel in self.storage.values():
56
+ if relationship == "sources_from":
57
+ if integration_slug in accel.get_sources_from_slugs():
58
+ result.append(accel)
59
+ elif relationship == "publishes_to":
60
+ if integration_slug in accel.get_publishes_to_slugs():
61
+ result.append(accel)
62
+ return result
63
+
64
+ async def get_dependents(self, accelerator_slug: str) -> list[Accelerator]:
65
+ """Get accelerators that depend on a specific accelerator."""
66
+ return [
67
+ accel
68
+ for accel in self.storage.values()
69
+ if accelerator_slug in accel.depends_on
70
+ ]
71
+
72
+ async def get_fed_by(self, accelerator_slug: str) -> list[Accelerator]:
73
+ """Get accelerators that feed into a specific accelerator."""
74
+ return [
75
+ accel
76
+ for accel in self.storage.values()
77
+ if accelerator_slug in accel.feeds_into
78
+ ]
79
+
80
+ async def get_all_statuses(self) -> set[str]:
81
+ """Get all unique statuses across all accelerators."""
82
+ return {
83
+ accel.status_normalized
84
+ for accel in self.storage.values()
85
+ if accel.status_normalized
86
+ }
@@ -0,0 +1,45 @@
1
+ """Memory implementation of AppRepository."""
2
+
3
+ import logging
4
+
5
+ from ...domain.models.app import App, AppType
6
+ from ...domain.repositories.app import AppRepository
7
+ from ...utils import normalize_name
8
+ from .base import MemoryRepositoryMixin
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class MemoryAppRepository(MemoryRepositoryMixin[App], AppRepository):
14
+ """In-memory implementation of AppRepository.
15
+
16
+ Apps are stored in a dictionary keyed by slug. This implementation
17
+ is used during Sphinx builds where apps are populated at builder-inited
18
+ and queried during doctree processing.
19
+ """
20
+
21
+ def __init__(self) -> None:
22
+ """Initialize with empty storage."""
23
+ self.storage: dict[str, App] = {}
24
+ self.entity_name = "App"
25
+ self.id_field = "slug"
26
+
27
+ async def get_by_type(self, app_type: AppType) -> list[App]:
28
+ """Get all apps of a specific type."""
29
+ return [app for app in self.storage.values() if app.app_type == app_type]
30
+
31
+ async def get_by_name(self, name: str) -> App | None:
32
+ """Get an app by its display name (case-insensitive)."""
33
+ name_normalized = normalize_name(name)
34
+ for app in self.storage.values():
35
+ if app.name_normalized == name_normalized:
36
+ return app
37
+ return None
38
+
39
+ async def get_all_types(self) -> set[AppType]:
40
+ """Get all unique app types that have apps."""
41
+ return {app.app_type for app in self.storage.values()}
42
+
43
+ async def get_apps_with_accelerators(self) -> list[App]:
44
+ """Get all apps that have accelerators defined."""
45
+ return [app for app in self.storage.values() if app.accelerators]
@@ -0,0 +1,106 @@
1
+ """Memory repository base classes and mixins for sphinx_hcd.
2
+
3
+ Provides common functionality for in-memory repository implementations,
4
+ following julee patterns but simplified for sphinx_hcd's needs.
5
+ """
6
+
7
+ import logging
8
+ from typing import Any, Generic, TypeVar
9
+
10
+ from pydantic import BaseModel
11
+
12
+ T = TypeVar("T", bound=BaseModel)
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class MemoryRepositoryMixin(Generic[T]):
18
+ """Mixin providing common repository patterns for memory implementations.
19
+
20
+ Encapsulates common functionality used across all memory repository
21
+ implementations:
22
+ - Dictionary-based entity storage and retrieval
23
+ - Standardized logging patterns
24
+ - Generic CRUD operations
25
+
26
+ Classes using this mixin must provide:
27
+ - self.storage: dict[str, T] for entity storage
28
+ - self.entity_name: str for logging
29
+ - self.id_field: str naming the entity's ID field
30
+ """
31
+
32
+ storage: dict[str, T]
33
+ entity_name: str
34
+ id_field: str
35
+
36
+ def _get_entity_id(self, entity: T) -> str:
37
+ """Extract the entity ID from an entity instance."""
38
+ return getattr(entity, self.id_field)
39
+
40
+ async def get(self, entity_id: str) -> T | None:
41
+ """Retrieve an entity by ID."""
42
+ entity = self.storage.get(entity_id)
43
+ if entity is None:
44
+ logger.debug(
45
+ f"Memory{self.entity_name}Repository: {self.entity_name} not found",
46
+ extra={f"{self.entity_name.lower()}_id": entity_id},
47
+ )
48
+ return entity
49
+
50
+ async def get_many(self, entity_ids: list[str]) -> dict[str, T | None]:
51
+ """Retrieve multiple entities by ID."""
52
+ result: dict[str, T | None] = {}
53
+ for entity_id in entity_ids:
54
+ result[entity_id] = self.storage.get(entity_id)
55
+ return result
56
+
57
+ async def save(self, entity: T) -> None:
58
+ """Save an entity to storage."""
59
+ entity_id = self._get_entity_id(entity)
60
+ self.storage[entity_id] = entity
61
+ logger.debug(
62
+ f"Memory{self.entity_name}Repository: Saved {self.entity_name}",
63
+ extra={f"{self.entity_name.lower()}_id": entity_id},
64
+ )
65
+
66
+ async def list_all(self) -> list[T]:
67
+ """List all entities."""
68
+ return list(self.storage.values())
69
+
70
+ async def delete(self, entity_id: str) -> bool:
71
+ """Delete an entity by ID."""
72
+ if entity_id in self.storage:
73
+ del self.storage[entity_id]
74
+ logger.debug(
75
+ f"Memory{self.entity_name}Repository: Deleted {self.entity_name}",
76
+ extra={f"{self.entity_name.lower()}_id": entity_id},
77
+ )
78
+ return True
79
+ return False
80
+
81
+ async def clear(self) -> None:
82
+ """Remove all entities from storage."""
83
+ count = len(self.storage)
84
+ self.storage.clear()
85
+ logger.debug(
86
+ f"Memory{self.entity_name}Repository: Cleared {count} entities",
87
+ )
88
+
89
+ # Additional query methods that subclasses can use
90
+
91
+ async def find_by_field(self, field: str, value: Any) -> list[T]:
92
+ """Find all entities where field equals value."""
93
+ return [
94
+ entity
95
+ for entity in self.storage.values()
96
+ if getattr(entity, field, None) == value
97
+ ]
98
+
99
+ async def find_by_field_in(self, field: str, values: list[Any]) -> list[T]:
100
+ """Find all entities where field is in values."""
101
+ value_set = set(values)
102
+ return [
103
+ entity
104
+ for entity in self.storage.values()
105
+ if getattr(entity, field, None) in value_set
106
+ ]
@@ -0,0 +1,59 @@
1
+ """Memory implementation of CodeInfoRepository."""
2
+
3
+ import logging
4
+
5
+ from ...domain.models.code_info import BoundedContextInfo
6
+ from ...domain.repositories.code_info import CodeInfoRepository
7
+ from .base import MemoryRepositoryMixin
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ class MemoryCodeInfoRepository(
13
+ MemoryRepositoryMixin[BoundedContextInfo], CodeInfoRepository
14
+ ):
15
+ """In-memory implementation of CodeInfoRepository.
16
+
17
+ Bounded context info is stored in a dictionary keyed by slug. This implementation
18
+ is used during Sphinx builds where code info is populated at builder-inited
19
+ by scanning src/ directories.
20
+ """
21
+
22
+ def __init__(self) -> None:
23
+ """Initialize with empty storage."""
24
+ self.storage: dict[str, BoundedContextInfo] = {}
25
+ self.entity_name = "BoundedContextInfo"
26
+ self.id_field = "slug"
27
+
28
+ async def get_by_code_dir(self, code_dir: str) -> BoundedContextInfo | None:
29
+ """Get bounded context info by its code directory name."""
30
+ for info in self.storage.values():
31
+ if info.code_dir == code_dir:
32
+ return info
33
+ return None
34
+
35
+ async def get_with_entities(self) -> list[BoundedContextInfo]:
36
+ """Get all bounded contexts that have domain entities."""
37
+ return [info for info in self.storage.values() if info.has_entities]
38
+
39
+ async def get_with_use_cases(self) -> list[BoundedContextInfo]:
40
+ """Get all bounded contexts that have use cases."""
41
+ return [info for info in self.storage.values() if info.has_use_cases]
42
+
43
+ async def get_with_infrastructure(self) -> list[BoundedContextInfo]:
44
+ """Get all bounded contexts that have infrastructure."""
45
+ return [info for info in self.storage.values() if info.has_infrastructure]
46
+
47
+ async def get_all_entity_names(self) -> set[str]:
48
+ """Get all unique entity class names across all bounded contexts."""
49
+ names: set[str] = set()
50
+ for info in self.storage.values():
51
+ names.update(info.get_entity_names())
52
+ return names
53
+
54
+ async def get_all_use_case_names(self) -> set[str]:
55
+ """Get all unique use case class names across all bounded contexts."""
56
+ names: set[str] = set()
57
+ for info in self.storage.values():
58
+ names.update(info.get_use_case_names())
59
+ return names
@@ -0,0 +1,54 @@
1
+ """Memory implementation of EpicRepository."""
2
+
3
+ import logging
4
+
5
+ from ...domain.models.epic import Epic
6
+ from ...domain.repositories.epic import EpicRepository
7
+ from ...utils import normalize_name
8
+ from .base import MemoryRepositoryMixin
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class MemoryEpicRepository(MemoryRepositoryMixin[Epic], EpicRepository):
14
+ """In-memory implementation of EpicRepository.
15
+
16
+ Epics are stored in a dictionary keyed by slug. This implementation
17
+ is used during Sphinx builds where epics are populated during doctree
18
+ processing and support incremental builds via docname tracking.
19
+ """
20
+
21
+ def __init__(self) -> None:
22
+ """Initialize with empty storage."""
23
+ self.storage: dict[str, Epic] = {}
24
+ self.entity_name = "Epic"
25
+ self.id_field = "slug"
26
+
27
+ async def get_by_docname(self, docname: str) -> list[Epic]:
28
+ """Get all epics defined in a specific document."""
29
+ return [epic for epic in self.storage.values() if epic.docname == docname]
30
+
31
+ async def clear_by_docname(self, docname: str) -> int:
32
+ """Remove all epics defined in a specific document."""
33
+ to_remove = [
34
+ slug for slug, epic in self.storage.items() if epic.docname == docname
35
+ ]
36
+ for slug in to_remove:
37
+ del self.storage[slug]
38
+ return len(to_remove)
39
+
40
+ async def get_with_story_ref(self, story_title: str) -> list[Epic]:
41
+ """Get epics that contain a specific story."""
42
+ story_normalized = normalize_name(story_title)
43
+ return [
44
+ epic
45
+ for epic in self.storage.values()
46
+ if any(normalize_name(ref) == story_normalized for ref in epic.story_refs)
47
+ ]
48
+
49
+ async def get_all_story_refs(self) -> set[str]:
50
+ """Get all unique story references across all epics."""
51
+ refs: set[str] = set()
52
+ for epic in self.storage.values():
53
+ refs.update(normalize_name(ref) for ref in epic.story_refs)
54
+ return refs
@@ -0,0 +1,70 @@
1
+ """Memory implementation of IntegrationRepository."""
2
+
3
+ import logging
4
+
5
+ from ...domain.models.integration import Direction, Integration
6
+ from ...domain.repositories.integration import IntegrationRepository
7
+ from ...utils import normalize_name
8
+ from .base import MemoryRepositoryMixin
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class MemoryIntegrationRepository(
14
+ MemoryRepositoryMixin[Integration], IntegrationRepository
15
+ ):
16
+ """In-memory implementation of IntegrationRepository.
17
+
18
+ Integrations are stored in a dictionary keyed by slug. This implementation
19
+ is used during Sphinx builds where integrations are populated at builder-inited
20
+ and queried during doctree processing.
21
+ """
22
+
23
+ def __init__(self) -> None:
24
+ """Initialize with empty storage."""
25
+ self.storage: dict[str, Integration] = {}
26
+ self.entity_name = "Integration"
27
+ self.id_field = "slug"
28
+
29
+ async def get_by_direction(self, direction: Direction) -> list[Integration]:
30
+ """Get all integrations with a specific direction."""
31
+ return [
32
+ integration
33
+ for integration in self.storage.values()
34
+ if integration.direction == direction
35
+ ]
36
+
37
+ async def get_by_module(self, module: str) -> Integration | None:
38
+ """Get an integration by its module name."""
39
+ for integration in self.storage.values():
40
+ if integration.module == module:
41
+ return integration
42
+ return None
43
+
44
+ async def get_by_name(self, name: str) -> Integration | None:
45
+ """Get an integration by its display name (case-insensitive)."""
46
+ name_normalized = normalize_name(name)
47
+ for integration in self.storage.values():
48
+ if integration.name_normalized == name_normalized:
49
+ return integration
50
+ return None
51
+
52
+ async def get_all_directions(self) -> set[Direction]:
53
+ """Get all unique directions that have integrations."""
54
+ return {integration.direction for integration in self.storage.values()}
55
+
56
+ async def get_with_dependencies(self) -> list[Integration]:
57
+ """Get all integrations that have external dependencies."""
58
+ return [
59
+ integration
60
+ for integration in self.storage.values()
61
+ if integration.depends_on
62
+ ]
63
+
64
+ async def get_by_dependency(self, dep_name: str) -> list[Integration]:
65
+ """Get all integrations that depend on a specific external system."""
66
+ return [
67
+ integration
68
+ for integration in self.storage.values()
69
+ if integration.has_dependency(dep_name)
70
+ ]
@@ -0,0 +1,96 @@
1
+ """Memory implementation of JourneyRepository."""
2
+
3
+ import logging
4
+
5
+ from ...domain.models.journey import Journey
6
+ from ...domain.repositories.journey import JourneyRepository
7
+ from ...utils import normalize_name
8
+ from .base import MemoryRepositoryMixin
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class MemoryJourneyRepository(MemoryRepositoryMixin[Journey], JourneyRepository):
14
+ """In-memory implementation of JourneyRepository.
15
+
16
+ Journeys are stored in a dictionary keyed by slug. This implementation
17
+ is used during Sphinx builds where journeys are populated during doctree
18
+ processing and support incremental builds via docname tracking.
19
+ """
20
+
21
+ def __init__(self) -> None:
22
+ """Initialize with empty storage."""
23
+ self.storage: dict[str, Journey] = {}
24
+ self.entity_name = "Journey"
25
+ self.id_field = "slug"
26
+
27
+ async def get_by_persona(self, persona: str) -> list[Journey]:
28
+ """Get all journeys for a persona."""
29
+ persona_normalized = normalize_name(persona)
30
+ return [
31
+ journey
32
+ for journey in self.storage.values()
33
+ if journey.persona_normalized == persona_normalized
34
+ ]
35
+
36
+ async def get_by_docname(self, docname: str) -> list[Journey]:
37
+ """Get all journeys defined in a specific document."""
38
+ return [
39
+ journey for journey in self.storage.values() if journey.docname == docname
40
+ ]
41
+
42
+ async def clear_by_docname(self, docname: str) -> int:
43
+ """Remove all journeys defined in a specific document."""
44
+ to_remove = [
45
+ slug for slug, journey in self.storage.items() if journey.docname == docname
46
+ ]
47
+ for slug in to_remove:
48
+ del self.storage[slug]
49
+ return len(to_remove)
50
+
51
+ async def get_dependents(self, journey_slug: str) -> list[Journey]:
52
+ """Get journeys that depend on a specific journey."""
53
+ return [
54
+ journey
55
+ for journey in self.storage.values()
56
+ if journey.has_dependency(journey_slug)
57
+ ]
58
+
59
+ async def get_dependencies(self, journey_slug: str) -> list[Journey]:
60
+ """Get journeys that a specific journey depends on."""
61
+ journey = self.storage.get(journey_slug)
62
+ if not journey:
63
+ return []
64
+ return [
65
+ self.storage[dep_slug]
66
+ for dep_slug in journey.depends_on
67
+ if dep_slug in self.storage
68
+ ]
69
+
70
+ async def get_all_personas(self) -> set[str]:
71
+ """Get all unique personas across all journeys."""
72
+ return {
73
+ journey.persona_normalized
74
+ for journey in self.storage.values()
75
+ if journey.persona_normalized
76
+ }
77
+
78
+ async def get_with_story_ref(self, story_title: str) -> list[Journey]:
79
+ """Get journeys that reference a specific story."""
80
+ story_normalized = normalize_name(story_title)
81
+ return [
82
+ journey
83
+ for journey in self.storage.values()
84
+ if any(
85
+ normalize_name(ref) == story_normalized
86
+ for ref in journey.get_story_refs()
87
+ )
88
+ ]
89
+
90
+ async def get_with_epic_ref(self, epic_slug: str) -> list[Journey]:
91
+ """Get journeys that reference a specific epic."""
92
+ return [
93
+ journey
94
+ for journey in self.storage.values()
95
+ if epic_slug in journey.get_epic_refs()
96
+ ]
@@ -0,0 +1,63 @@
1
+ """Memory implementation of StoryRepository."""
2
+
3
+ import logging
4
+
5
+ from ...domain.models.story import Story
6
+ from ...domain.repositories.story import StoryRepository
7
+ from ...utils import normalize_name
8
+ from .base import MemoryRepositoryMixin
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class MemoryStoryRepository(MemoryRepositoryMixin[Story], StoryRepository):
14
+ """In-memory implementation of StoryRepository.
15
+
16
+ Stories are stored in a dictionary keyed by slug. This implementation
17
+ is used during Sphinx builds where stories are populated at builder-inited
18
+ and queried during doctree processing.
19
+ """
20
+
21
+ def __init__(self) -> None:
22
+ """Initialize with empty storage."""
23
+ self.storage: dict[str, Story] = {}
24
+ self.entity_name = "Story"
25
+ self.id_field = "slug"
26
+
27
+ async def get_by_app(self, app_slug: str) -> list[Story]:
28
+ """Get all stories for an application."""
29
+ app_normalized = normalize_name(app_slug)
30
+ return [
31
+ story
32
+ for story in self.storage.values()
33
+ if story.app_normalized == app_normalized
34
+ ]
35
+
36
+ async def get_by_persona(self, persona: str) -> list[Story]:
37
+ """Get all stories for a persona."""
38
+ persona_normalized = normalize_name(persona)
39
+ return [
40
+ story
41
+ for story in self.storage.values()
42
+ if story.persona_normalized == persona_normalized
43
+ ]
44
+
45
+ async def get_by_feature_title(self, feature_title: str) -> Story | None:
46
+ """Get a story by its feature title."""
47
+ title_normalized = normalize_name(feature_title)
48
+ for story in self.storage.values():
49
+ if normalize_name(story.feature_title) == title_normalized:
50
+ return story
51
+ return None
52
+
53
+ async def get_apps_with_stories(self) -> set[str]:
54
+ """Get the set of app slugs that have stories."""
55
+ return {story.app_slug for story in self.storage.values()}
56
+
57
+ async def get_all_personas(self) -> set[str]:
58
+ """Get all unique personas across all stories."""
59
+ return {
60
+ story.persona_normalized
61
+ for story in self.storage.values()
62
+ if story.persona_normalized != "unknown"
63
+ }
@@ -0,0 +1,28 @@
1
+ """Sphinx application layer for sphinx_hcd.
2
+
3
+ Contains Sphinx-specific code:
4
+ - adapters.py: SyncRepositoryAdapter for sync access to async repos
5
+ - context.py: HCDContext for unified repository access
6
+ - initialization.py: Builder-inited handlers
7
+ - directives/: Sphinx directive implementations
8
+ - event_handlers/: Sphinx lifecycle event handlers
9
+ """
10
+
11
+ from .adapters import SyncRepositoryAdapter
12
+ from .context import (
13
+ HCDContext,
14
+ ensure_hcd_context,
15
+ get_hcd_context,
16
+ set_hcd_context,
17
+ )
18
+ from .initialization import initialize_hcd_context, purge_doc_from_context
19
+
20
+ __all__ = [
21
+ "HCDContext",
22
+ "SyncRepositoryAdapter",
23
+ "ensure_hcd_context",
24
+ "get_hcd_context",
25
+ "initialize_hcd_context",
26
+ "purge_doc_from_context",
27
+ "set_hcd_context",
28
+ ]