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,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
+ ]
@@ -0,0 +1,116 @@
1
+ """Sync adapters for async repositories.
2
+
3
+ Sphinx directives are synchronous, but our domain repositories are async
4
+ (following julee patterns). This module provides adapters to bridge the gap.
5
+ """
6
+
7
+ import asyncio
8
+ from typing import Any, Generic, TypeVar
9
+
10
+ from pydantic import BaseModel
11
+
12
+ from ..domain.repositories.base import BaseRepository
13
+
14
+ T = TypeVar("T", bound=BaseModel)
15
+
16
+
17
+ class SyncRepositoryAdapter(Generic[T]):
18
+ """Synchronous wrapper for async repository methods.
19
+
20
+ Provides a synchronous interface to async repositories for use in
21
+ Sphinx directives. Uses asyncio.run() to execute async methods.
22
+
23
+ Example:
24
+ >>> async_repo = MemoryStoryRepository()
25
+ >>> sync_repo = SyncRepositoryAdapter(async_repo)
26
+ >>> story = sync_repo.get("my-story-slug") # Sync call
27
+
28
+ Note:
29
+ This adapter is designed for use in Sphinx's synchronous directive
30
+ system. The overhead of asyncio.run() is negligible for in-memory
31
+ repositories.
32
+ """
33
+
34
+ def __init__(self, async_repo: BaseRepository[T]) -> None:
35
+ """Initialize with an async repository.
36
+
37
+ Args:
38
+ async_repo: An async repository implementing BaseRepository[T]
39
+ """
40
+ self._repo = async_repo
41
+
42
+ @property
43
+ def async_repo(self) -> BaseRepository[T]:
44
+ """Access the underlying async repository."""
45
+ return self._repo
46
+
47
+ def get(self, entity_id: str) -> T | None:
48
+ """Retrieve an entity by ID (sync wrapper).
49
+
50
+ Args:
51
+ entity_id: Unique entity identifier
52
+
53
+ Returns:
54
+ Entity if found, None otherwise
55
+ """
56
+ return asyncio.run(self._repo.get(entity_id))
57
+
58
+ def get_many(self, entity_ids: list[str]) -> dict[str, T | None]:
59
+ """Retrieve multiple entities by ID (sync wrapper).
60
+
61
+ Args:
62
+ entity_ids: List of unique entity identifiers
63
+
64
+ Returns:
65
+ Dict mapping entity_id to entity (or None if not found)
66
+ """
67
+ return asyncio.run(self._repo.get_many(entity_ids))
68
+
69
+ def save(self, entity: T) -> None:
70
+ """Save an entity (sync wrapper).
71
+
72
+ Args:
73
+ entity: Complete entity to save
74
+ """
75
+ asyncio.run(self._repo.save(entity))
76
+
77
+ def list_all(self) -> list[T]:
78
+ """List all entities (sync wrapper).
79
+
80
+ Returns:
81
+ List of all entities in the repository
82
+ """
83
+ return asyncio.run(self._repo.list_all())
84
+
85
+ def delete(self, entity_id: str) -> bool:
86
+ """Delete an entity by ID (sync wrapper).
87
+
88
+ Args:
89
+ entity_id: Unique entity identifier
90
+
91
+ Returns:
92
+ True if entity was deleted, False if not found
93
+ """
94
+ return asyncio.run(self._repo.delete(entity_id))
95
+
96
+ def clear(self) -> None:
97
+ """Remove all entities from the repository (sync wrapper)."""
98
+ asyncio.run(self._repo.clear())
99
+
100
+ def run_async(self, coro: Any) -> Any:
101
+ """Run an arbitrary async method on the underlying repository.
102
+
103
+ Useful for repository-specific methods not in BaseRepository.
104
+
105
+ Args:
106
+ coro: A coroutine to execute
107
+
108
+ Returns:
109
+ The result of the coroutine
110
+
111
+ Example:
112
+ >>> result = sync_repo.run_async(
113
+ ... sync_repo.async_repo.find_by_persona("Staff Member")
114
+ ... )
115
+ """
116
+ return asyncio.run(coro)
@@ -0,0 +1,163 @@
1
+ """HCDContext for unified repository access.
2
+
3
+ Provides a single context object that holds all repositories for the
4
+ HCD documentation system. This replaces the scattered global/env registries
5
+ with a unified, type-safe interface.
6
+ """
7
+
8
+ from dataclasses import dataclass, field
9
+ from typing import TYPE_CHECKING
10
+
11
+ from ..repositories.memory import (
12
+ MemoryAcceleratorRepository,
13
+ MemoryAppRepository,
14
+ MemoryCodeInfoRepository,
15
+ MemoryEpicRepository,
16
+ MemoryIntegrationRepository,
17
+ MemoryJourneyRepository,
18
+ MemoryStoryRepository,
19
+ )
20
+ from .adapters import SyncRepositoryAdapter
21
+
22
+ if TYPE_CHECKING:
23
+ from ..domain.models import (
24
+ Accelerator,
25
+ App,
26
+ BoundedContextInfo,
27
+ Epic,
28
+ Integration,
29
+ Journey,
30
+ Story,
31
+ )
32
+
33
+
34
+ @dataclass
35
+ class HCDContext:
36
+ """Unified context for HCD documentation.
37
+
38
+ Holds all repositories needed for the HCD documentation system.
39
+ Each repository is wrapped in a SyncRepositoryAdapter for use in
40
+ Sphinx's synchronous directive system.
41
+
42
+ This context is created at builder-inited and attached to the
43
+ Sphinx app object. It can be retrieved using get_hcd_context().
44
+
45
+ Attributes:
46
+ story_repo: Repository for Story entities
47
+ journey_repo: Repository for Journey entities
48
+ epic_repo: Repository for Epic entities
49
+ app_repo: Repository for App entities
50
+ accelerator_repo: Repository for Accelerator entities
51
+ integration_repo: Repository for Integration entities
52
+ code_info_repo: Repository for BoundedContextInfo entities
53
+ """
54
+
55
+ story_repo: SyncRepositoryAdapter["Story"] = field(
56
+ default_factory=lambda: SyncRepositoryAdapter(MemoryStoryRepository())
57
+ )
58
+ journey_repo: SyncRepositoryAdapter["Journey"] = field(
59
+ default_factory=lambda: SyncRepositoryAdapter(MemoryJourneyRepository())
60
+ )
61
+ epic_repo: SyncRepositoryAdapter["Epic"] = field(
62
+ default_factory=lambda: SyncRepositoryAdapter(MemoryEpicRepository())
63
+ )
64
+ app_repo: SyncRepositoryAdapter["App"] = field(
65
+ default_factory=lambda: SyncRepositoryAdapter(MemoryAppRepository())
66
+ )
67
+ accelerator_repo: SyncRepositoryAdapter["Accelerator"] = field(
68
+ default_factory=lambda: SyncRepositoryAdapter(MemoryAcceleratorRepository())
69
+ )
70
+ integration_repo: SyncRepositoryAdapter["Integration"] = field(
71
+ default_factory=lambda: SyncRepositoryAdapter(MemoryIntegrationRepository())
72
+ )
73
+ code_info_repo: SyncRepositoryAdapter["BoundedContextInfo"] = field(
74
+ default_factory=lambda: SyncRepositoryAdapter(MemoryCodeInfoRepository())
75
+ )
76
+
77
+ def clear_all(self) -> None:
78
+ """Clear all repositories.
79
+
80
+ Useful for testing or when rebuilding documentation from scratch.
81
+ """
82
+ self.story_repo.clear()
83
+ self.journey_repo.clear()
84
+ self.epic_repo.clear()
85
+ self.app_repo.clear()
86
+ self.accelerator_repo.clear()
87
+ self.integration_repo.clear()
88
+ self.code_info_repo.clear()
89
+
90
+ def clear_by_docname(self, docname: str) -> dict[str, int]:
91
+ """Clear entities defined in a specific document.
92
+
93
+ Used during incremental builds when a document is re-read.
94
+ Only entities that track docname are cleared (journey, epic, accelerator).
95
+
96
+ Args:
97
+ docname: RST document name
98
+
99
+ Returns:
100
+ Dict mapping entity type to number of entities removed
101
+ """
102
+ results = {}
103
+
104
+ # Journey repo has clear_by_docname
105
+ journey_async = self.journey_repo.async_repo
106
+ results["journeys"] = self.journey_repo.run_async(
107
+ journey_async.clear_by_docname(docname) # type: ignore
108
+ )
109
+
110
+ # Epic repo has clear_by_docname
111
+ epic_async = self.epic_repo.async_repo
112
+ results["epics"] = self.epic_repo.run_async(
113
+ epic_async.clear_by_docname(docname) # type: ignore
114
+ )
115
+
116
+ # Accelerator repo has clear_by_docname
117
+ accel_async = self.accelerator_repo.async_repo
118
+ results["accelerators"] = self.accelerator_repo.run_async(
119
+ accel_async.clear_by_docname(docname) # type: ignore
120
+ )
121
+
122
+ return results
123
+
124
+
125
+ def get_hcd_context(app) -> HCDContext:
126
+ """Get the HCDContext from a Sphinx app.
127
+
128
+ Args:
129
+ app: Sphinx application object
130
+
131
+ Returns:
132
+ HCDContext attached to the app
133
+
134
+ Raises:
135
+ AttributeError: If context hasn't been initialized
136
+ """
137
+ return app._hcd_context
138
+
139
+
140
+ def set_hcd_context(app, context: HCDContext) -> None:
141
+ """Set the HCDContext on a Sphinx app.
142
+
143
+ Args:
144
+ app: Sphinx application object
145
+ context: HCDContext to attach
146
+ """
147
+ app._hcd_context = context
148
+
149
+
150
+ def ensure_hcd_context(app) -> HCDContext:
151
+ """Ensure the HCDContext exists on a Sphinx app.
152
+
153
+ Creates a new context if one doesn't exist.
154
+
155
+ Args:
156
+ app: Sphinx application object
157
+
158
+ Returns:
159
+ HCDContext attached to the app
160
+ """
161
+ if not hasattr(app, "_hcd_context"):
162
+ set_hcd_context(app, HCDContext())
163
+ return get_hcd_context(app)
@@ -0,0 +1,160 @@
1
+ """Sphinx directives for sphinx_hcd.
2
+
3
+ Thin directive adapters that use domain models and repositories.
4
+ """
5
+
6
+ from .accelerator import (
7
+ AcceleratorDependencyDiagramDirective,
8
+ AcceleratorDependencyDiagramPlaceholder,
9
+ AcceleratorIndexDirective,
10
+ AcceleratorIndexPlaceholder,
11
+ AcceleratorsForAppDirective,
12
+ AcceleratorsForAppPlaceholder,
13
+ AcceleratorStatusDirective,
14
+ DefineAcceleratorDirective,
15
+ DefineAcceleratorPlaceholder,
16
+ DependentAcceleratorsDirective,
17
+ DependentAcceleratorsPlaceholder,
18
+ clear_accelerator_state,
19
+ process_accelerator_placeholders,
20
+ )
21
+ from .app import (
22
+ AppIndexDirective,
23
+ AppIndexPlaceholder,
24
+ AppsForPersonaDirective,
25
+ AppsForPersonaPlaceholder,
26
+ DefineAppDirective,
27
+ DefineAppPlaceholder,
28
+ process_app_placeholders,
29
+ )
30
+ from .base import HCDDirective, make_deprecated_directive
31
+ from .epic import (
32
+ DefineEpicDirective,
33
+ EpicIndexDirective,
34
+ EpicIndexPlaceholder,
35
+ EpicsForPersonaDirective,
36
+ EpicsForPersonaPlaceholder,
37
+ EpicStoryDirective,
38
+ clear_epic_state,
39
+ process_epic_placeholders,
40
+ )
41
+ from .integration import (
42
+ DefineIntegrationDirective,
43
+ DefineIntegrationPlaceholder,
44
+ IntegrationIndexDirective,
45
+ IntegrationIndexPlaceholder,
46
+ process_integration_placeholders,
47
+ )
48
+ from .journey import (
49
+ DefineJourneyDirective,
50
+ JourneyDependencyGraphDirective,
51
+ JourneyDependencyGraphPlaceholder,
52
+ JourneyIndexDirective,
53
+ JourneysForPersonaDirective,
54
+ StepEpicDirective,
55
+ StepPhaseDirective,
56
+ StepStoryDirective,
57
+ clear_journey_state,
58
+ process_dependency_graph_placeholder,
59
+ process_journey_steps,
60
+ )
61
+ from .persona import (
62
+ PersonaDiagramDirective,
63
+ PersonaDiagramPlaceholder,
64
+ PersonaIndexDiagramDirective,
65
+ PersonaIndexDiagramPlaceholder,
66
+ process_persona_placeholders,
67
+ )
68
+ from .story import (
69
+ GherkinAppStoriesDirective,
70
+ GherkinStoriesDirective,
71
+ GherkinStoriesForAppDirective,
72
+ GherkinStoriesForPersonaDirective,
73
+ GherkinStoriesIndexDirective,
74
+ GherkinStoryDirective,
75
+ StoriesDirective,
76
+ StoryAppDirective,
77
+ StoryIndexDirective,
78
+ StoryListForAppDirective,
79
+ StoryListForPersonaDirective,
80
+ StoryRefDirective,
81
+ StorySeeAlsoPlaceholder,
82
+ process_story_seealso_placeholders,
83
+ )
84
+
85
+ __all__ = [
86
+ # Base
87
+ "HCDDirective",
88
+ "make_deprecated_directive",
89
+ # Story directives
90
+ "StoryAppDirective",
91
+ "StoryListForPersonaDirective",
92
+ "StoryListForAppDirective",
93
+ "StoryIndexDirective",
94
+ "StoriesDirective",
95
+ "StoryRefDirective",
96
+ "StorySeeAlsoPlaceholder",
97
+ "process_story_seealso_placeholders",
98
+ # Story deprecated aliases
99
+ "GherkinStoryDirective",
100
+ "GherkinStoriesDirective",
101
+ "GherkinStoriesForPersonaDirective",
102
+ "GherkinStoriesForAppDirective",
103
+ "GherkinStoriesIndexDirective",
104
+ "GherkinAppStoriesDirective",
105
+ # Journey directives
106
+ "DefineJourneyDirective",
107
+ "StepStoryDirective",
108
+ "StepEpicDirective",
109
+ "StepPhaseDirective",
110
+ "JourneyIndexDirective",
111
+ "JourneyDependencyGraphDirective",
112
+ "JourneyDependencyGraphPlaceholder",
113
+ "JourneysForPersonaDirective",
114
+ "clear_journey_state",
115
+ "process_journey_steps",
116
+ "process_dependency_graph_placeholder",
117
+ # Epic directives
118
+ "DefineEpicDirective",
119
+ "EpicStoryDirective",
120
+ "EpicIndexDirective",
121
+ "EpicIndexPlaceholder",
122
+ "EpicsForPersonaDirective",
123
+ "EpicsForPersonaPlaceholder",
124
+ "clear_epic_state",
125
+ "process_epic_placeholders",
126
+ # App directives
127
+ "DefineAppDirective",
128
+ "DefineAppPlaceholder",
129
+ "AppIndexDirective",
130
+ "AppIndexPlaceholder",
131
+ "AppsForPersonaDirective",
132
+ "AppsForPersonaPlaceholder",
133
+ "process_app_placeholders",
134
+ # Accelerator directives
135
+ "DefineAcceleratorDirective",
136
+ "DefineAcceleratorPlaceholder",
137
+ "AcceleratorIndexDirective",
138
+ "AcceleratorIndexPlaceholder",
139
+ "AcceleratorsForAppDirective",
140
+ "AcceleratorsForAppPlaceholder",
141
+ "DependentAcceleratorsDirective",
142
+ "DependentAcceleratorsPlaceholder",
143
+ "AcceleratorDependencyDiagramDirective",
144
+ "AcceleratorDependencyDiagramPlaceholder",
145
+ "AcceleratorStatusDirective",
146
+ "clear_accelerator_state",
147
+ "process_accelerator_placeholders",
148
+ # Integration directives
149
+ "DefineIntegrationDirective",
150
+ "DefineIntegrationPlaceholder",
151
+ "IntegrationIndexDirective",
152
+ "IntegrationIndexPlaceholder",
153
+ "process_integration_placeholders",
154
+ # Persona directives
155
+ "PersonaDiagramDirective",
156
+ "PersonaDiagramPlaceholder",
157
+ "PersonaIndexDiagramDirective",
158
+ "PersonaIndexDiagramPlaceholder",
159
+ "process_persona_placeholders",
160
+ ]