julee-viewpoints 0.1.0__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 (102) hide show
  1. julee_viewpoints/__init__.py +26 -0
  2. julee_viewpoints/py.typed +0 -0
  3. julee_viewpoints/sphinx_hcd/__init__.py +209 -0
  4. julee_viewpoints/sphinx_hcd/config.py +149 -0
  5. julee_viewpoints/sphinx_hcd/domain/__init__.py +5 -0
  6. julee_viewpoints/sphinx_hcd/domain/models/__init__.py +32 -0
  7. julee_viewpoints/sphinx_hcd/domain/models/accelerator.py +158 -0
  8. julee_viewpoints/sphinx_hcd/domain/models/app.py +152 -0
  9. julee_viewpoints/sphinx_hcd/domain/models/code_info.py +124 -0
  10. julee_viewpoints/sphinx_hcd/domain/models/epic.py +86 -0
  11. julee_viewpoints/sphinx_hcd/domain/models/integration.py +226 -0
  12. julee_viewpoints/sphinx_hcd/domain/models/journey.py +231 -0
  13. julee_viewpoints/sphinx_hcd/domain/models/persona.py +112 -0
  14. julee_viewpoints/sphinx_hcd/domain/models/story.py +141 -0
  15. julee_viewpoints/sphinx_hcd/domain/repositories/__init__.py +25 -0
  16. julee_viewpoints/sphinx_hcd/domain/repositories/accelerator.py +98 -0
  17. julee_viewpoints/sphinx_hcd/domain/repositories/app.py +57 -0
  18. julee_viewpoints/sphinx_hcd/domain/repositories/base.py +89 -0
  19. julee_viewpoints/sphinx_hcd/domain/repositories/code_info.py +69 -0
  20. julee_viewpoints/sphinx_hcd/domain/repositories/epic.py +62 -0
  21. julee_viewpoints/sphinx_hcd/domain/repositories/integration.py +79 -0
  22. julee_viewpoints/sphinx_hcd/domain/repositories/journey.py +106 -0
  23. julee_viewpoints/sphinx_hcd/domain/repositories/story.py +68 -0
  24. julee_viewpoints/sphinx_hcd/parsers/__init__.py +48 -0
  25. julee_viewpoints/sphinx_hcd/parsers/ast.py +152 -0
  26. julee_viewpoints/sphinx_hcd/parsers/gherkin.py +155 -0
  27. julee_viewpoints/sphinx_hcd/parsers/yaml.py +185 -0
  28. julee_viewpoints/sphinx_hcd/repositories/__init__.py +4 -0
  29. julee_viewpoints/sphinx_hcd/repositories/memory/__init__.py +25 -0
  30. julee_viewpoints/sphinx_hcd/repositories/memory/accelerator.py +86 -0
  31. julee_viewpoints/sphinx_hcd/repositories/memory/app.py +45 -0
  32. julee_viewpoints/sphinx_hcd/repositories/memory/base.py +107 -0
  33. julee_viewpoints/sphinx_hcd/repositories/memory/code_info.py +59 -0
  34. julee_viewpoints/sphinx_hcd/repositories/memory/epic.py +54 -0
  35. julee_viewpoints/sphinx_hcd/repositories/memory/integration.py +70 -0
  36. julee_viewpoints/sphinx_hcd/repositories/memory/journey.py +96 -0
  37. julee_viewpoints/sphinx_hcd/repositories/memory/story.py +63 -0
  38. julee_viewpoints/sphinx_hcd/sphinx/__init__.py +28 -0
  39. julee_viewpoints/sphinx_hcd/sphinx/adapters.py +116 -0
  40. julee_viewpoints/sphinx_hcd/sphinx/context.py +163 -0
  41. julee_viewpoints/sphinx_hcd/sphinx/directives/__init__.py +160 -0
  42. julee_viewpoints/sphinx_hcd/sphinx/directives/accelerator.py +581 -0
  43. julee_viewpoints/sphinx_hcd/sphinx/directives/app.py +349 -0
  44. julee_viewpoints/sphinx_hcd/sphinx/directives/base.py +212 -0
  45. julee_viewpoints/sphinx_hcd/sphinx/directives/epic.py +439 -0
  46. julee_viewpoints/sphinx_hcd/sphinx/directives/integration.py +220 -0
  47. julee_viewpoints/sphinx_hcd/sphinx/directives/journey.py +652 -0
  48. julee_viewpoints/sphinx_hcd/sphinx/directives/persona.py +347 -0
  49. julee_viewpoints/sphinx_hcd/sphinx/directives/story.py +575 -0
  50. julee_viewpoints/sphinx_hcd/sphinx/event_handlers/__init__.py +16 -0
  51. julee_viewpoints/sphinx_hcd/sphinx/event_handlers/builder_inited.py +31 -0
  52. julee_viewpoints/sphinx_hcd/sphinx/event_handlers/doctree_read.py +27 -0
  53. julee_viewpoints/sphinx_hcd/sphinx/event_handlers/doctree_resolved.py +43 -0
  54. julee_viewpoints/sphinx_hcd/sphinx/event_handlers/env_purge_doc.py +42 -0
  55. julee_viewpoints/sphinx_hcd/sphinx/initialization.py +139 -0
  56. julee_viewpoints/sphinx_hcd/tests/__init__.py +9 -0
  57. julee_viewpoints/sphinx_hcd/tests/conftest.py +6 -0
  58. julee_viewpoints/sphinx_hcd/tests/domain/__init__.py +1 -0
  59. julee_viewpoints/sphinx_hcd/tests/domain/models/__init__.py +1 -0
  60. julee_viewpoints/sphinx_hcd/tests/domain/models/test_accelerator.py +272 -0
  61. julee_viewpoints/sphinx_hcd/tests/domain/models/test_app.py +261 -0
  62. julee_viewpoints/sphinx_hcd/tests/domain/models/test_code_info.py +229 -0
  63. julee_viewpoints/sphinx_hcd/tests/domain/models/test_epic.py +177 -0
  64. julee_viewpoints/sphinx_hcd/tests/domain/models/test_integration.py +327 -0
  65. julee_viewpoints/sphinx_hcd/tests/domain/models/test_journey.py +258 -0
  66. julee_viewpoints/sphinx_hcd/tests/domain/models/test_persona.py +187 -0
  67. julee_viewpoints/sphinx_hcd/tests/domain/models/test_story.py +216 -0
  68. julee_viewpoints/sphinx_hcd/tests/integration/__init__.py +1 -0
  69. julee_viewpoints/sphinx_hcd/tests/parsers/__init__.py +1 -0
  70. julee_viewpoints/sphinx_hcd/tests/parsers/test_ast.py +279 -0
  71. julee_viewpoints/sphinx_hcd/tests/parsers/test_gherkin.py +280 -0
  72. julee_viewpoints/sphinx_hcd/tests/parsers/test_yaml.py +480 -0
  73. julee_viewpoints/sphinx_hcd/tests/repositories/__init__.py +1 -0
  74. julee_viewpoints/sphinx_hcd/tests/repositories/test_accelerator.py +298 -0
  75. julee_viewpoints/sphinx_hcd/tests/repositories/test_app.py +218 -0
  76. julee_viewpoints/sphinx_hcd/tests/repositories/test_base.py +151 -0
  77. julee_viewpoints/sphinx_hcd/tests/repositories/test_code_info.py +253 -0
  78. julee_viewpoints/sphinx_hcd/tests/repositories/test_epic.py +237 -0
  79. julee_viewpoints/sphinx_hcd/tests/repositories/test_integration.py +268 -0
  80. julee_viewpoints/sphinx_hcd/tests/repositories/test_journey.py +296 -0
  81. julee_viewpoints/sphinx_hcd/tests/repositories/test_story.py +236 -0
  82. julee_viewpoints/sphinx_hcd/tests/sphinx/__init__.py +1 -0
  83. julee_viewpoints/sphinx_hcd/tests/sphinx/directives/__init__.py +1 -0
  84. julee_viewpoints/sphinx_hcd/tests/sphinx/directives/test_base.py +160 -0
  85. julee_viewpoints/sphinx_hcd/tests/sphinx/test_adapters.py +176 -0
  86. julee_viewpoints/sphinx_hcd/tests/sphinx/test_context.py +257 -0
  87. julee_viewpoints/sphinx_hcd/tests/usecases/__init__.py +1 -0
  88. julee_viewpoints/sphinx_hcd/tests/usecases/test_derive_personas.py +314 -0
  89. julee_viewpoints/sphinx_hcd/tests/usecases/test_resolve_accelerator_references.py +489 -0
  90. julee_viewpoints/sphinx_hcd/tests/usecases/test_resolve_app_references.py +276 -0
  91. julee_viewpoints/sphinx_hcd/tests/usecases/test_resolve_story_references.py +240 -0
  92. julee_viewpoints/sphinx_hcd/usecases/__init__.py +83 -0
  93. julee_viewpoints/sphinx_hcd/usecases/derive_personas.py +203 -0
  94. julee_viewpoints/sphinx_hcd/usecases/resolve_accelerator_references.py +275 -0
  95. julee_viewpoints/sphinx_hcd/usecases/resolve_app_references.py +168 -0
  96. julee_viewpoints/sphinx_hcd/usecases/resolve_story_references.py +147 -0
  97. julee_viewpoints/sphinx_hcd/utils.py +185 -0
  98. julee_viewpoints-0.1.0.dist-info/METADATA +34 -0
  99. julee_viewpoints-0.1.0.dist-info/RECORD +102 -0
  100. julee_viewpoints-0.1.0.dist-info/WHEEL +5 -0
  101. julee_viewpoints-0.1.0.dist-info/entry_points.txt +2 -0
  102. julee_viewpoints-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,26 @@
1
+ """Code-outward documentation for julee solutions.
2
+
3
+ This kit projects a solution into documentation: its personas, journeys,
4
+ epics and stories, its applications and integrations, and the bounded
5
+ contexts its code already describes (ADR 006).
6
+
7
+ It is a viewpoint kit. Its bounded contexts describe a solution rather
8
+ than implement part of one, which is why the manifest sets
9
+ ``viewpoint=True``.
10
+
11
+ Use it in a solution's ``conf.py``::
12
+
13
+ extensions = ["julee_viewpoints.sphinx_hcd"]
14
+ """
15
+
16
+ from julee.core.entities.kit import Kit
17
+
18
+ kit = Kit(
19
+ slug="viewpoints",
20
+ name="Code-outward documentation",
21
+ package="julee_viewpoints",
22
+ viewpoint=True,
23
+ contributes={"sphinx.extension": "julee_viewpoints.sphinx_hcd"},
24
+ )
25
+
26
+ __all__ = ["kit"]
File without changes
@@ -0,0 +1,209 @@
1
+ """Sphinx HCD (Human-Centered Design) Extensions for Julee Solutions.
2
+
3
+ This package provides Sphinx extensions for documenting Julee-based solutions
4
+ using Human-Centered Design patterns. It supports:
5
+
6
+ - Stories: User stories derived from Gherkin .feature files
7
+ - Journeys: User journeys composed of stories and epics
8
+ - Epics: Collections of related stories
9
+ - Apps: Application documentation with manifest-based metadata
10
+ - Accelerators: Domain accelerator documentation with bounded context scanning
11
+ - Integrations: External integration documentation
12
+ - Personas: Auto-generated UML diagrams showing persona-epic-app relationships
13
+
14
+ Usage in conf.py::
15
+
16
+ extensions = ["julee_viewpoints.sphinx_hcd"]
17
+
18
+ # Optional configuration (defaults match standard Julee layout)
19
+ sphinx_hcd = {
20
+ 'paths': {
21
+ 'feature_files': 'tests/e2e/',
22
+ 'app_manifests': 'apps/',
23
+ 'integration_manifests': 'src/integrations/',
24
+ 'bounded_contexts': 'src/',
25
+ },
26
+ 'docs_structure': {
27
+ 'applications': 'applications',
28
+ 'personas': 'users/personas',
29
+ 'journeys': 'users/journeys',
30
+ 'epics': 'users/epics',
31
+ 'accelerators': 'domain/accelerators',
32
+ 'integrations': 'integrations',
33
+ 'stories': 'users/stories',
34
+ },
35
+ }
36
+ """
37
+
38
+ from sphinx.util import logging
39
+
40
+ from .config import init_config
41
+
42
+ logger = logging.getLogger(__name__)
43
+
44
+
45
+ def setup(app):
46
+ """Set up all HCD extensions for Sphinx."""
47
+ from .sphinx.directives import (
48
+ AcceleratorDependencyDiagramDirective,
49
+ AcceleratorDependencyDiagramPlaceholder,
50
+ AcceleratorIndexDirective,
51
+ AcceleratorIndexPlaceholder,
52
+ AcceleratorsForAppDirective,
53
+ AcceleratorsForAppPlaceholder,
54
+ AcceleratorStatusDirective,
55
+ AppIndexDirective,
56
+ AppIndexPlaceholder,
57
+ AppsForPersonaDirective,
58
+ AppsForPersonaPlaceholder,
59
+ # Accelerator directives
60
+ DefineAcceleratorDirective,
61
+ DefineAcceleratorPlaceholder,
62
+ # App directives
63
+ DefineAppDirective,
64
+ DefineAppPlaceholder,
65
+ # Epic directives
66
+ DefineEpicDirective,
67
+ # Integration directives
68
+ DefineIntegrationDirective,
69
+ DefineIntegrationPlaceholder,
70
+ # Journey directives
71
+ DefineJourneyDirective,
72
+ DependentAcceleratorsDirective,
73
+ DependentAcceleratorsPlaceholder,
74
+ EpicIndexDirective,
75
+ EpicIndexPlaceholder,
76
+ EpicsForPersonaDirective,
77
+ EpicsForPersonaPlaceholder,
78
+ EpicStoryDirective,
79
+ GherkinAppStoriesDirective,
80
+ GherkinStoriesDirective,
81
+ GherkinStoriesForAppDirective,
82
+ GherkinStoriesForPersonaDirective,
83
+ GherkinStoriesIndexDirective,
84
+ # Story deprecated aliases
85
+ GherkinStoryDirective,
86
+ IntegrationIndexDirective,
87
+ IntegrationIndexPlaceholder,
88
+ JourneyDependencyGraphDirective,
89
+ JourneyDependencyGraphPlaceholder,
90
+ JourneyIndexDirective,
91
+ JourneysForPersonaDirective,
92
+ # Persona directives
93
+ PersonaDiagramDirective,
94
+ PersonaDiagramPlaceholder,
95
+ PersonaIndexDiagramDirective,
96
+ PersonaIndexDiagramPlaceholder,
97
+ StepEpicDirective,
98
+ StepPhaseDirective,
99
+ StepStoryDirective,
100
+ StoriesDirective,
101
+ # Story directives
102
+ StoryAppDirective,
103
+ StoryIndexDirective,
104
+ StoryListForAppDirective,
105
+ StoryListForPersonaDirective,
106
+ StoryRefDirective,
107
+ StorySeeAlsoPlaceholder,
108
+ )
109
+ from .sphinx.event_handlers import (
110
+ on_builder_inited,
111
+ on_doctree_read,
112
+ on_doctree_resolved,
113
+ on_env_purge_doc,
114
+ )
115
+
116
+ # Register configuration value first
117
+ app.add_config_value("sphinx_hcd", {}, "env")
118
+
119
+ # Initialize config when builder starts (after conf.py is loaded)
120
+ app.connect("builder-inited", _init_config_handler, priority=0)
121
+
122
+ # Connect event handlers
123
+ app.connect("builder-inited", on_builder_inited, priority=100)
124
+ app.connect("doctree-read", on_doctree_read)
125
+ app.connect("doctree-resolved", on_doctree_resolved)
126
+ app.connect("env-purge-doc", on_env_purge_doc)
127
+
128
+ # Register story directives
129
+ app.add_directive("story", StoryRefDirective)
130
+ app.add_directive("stories", StoriesDirective)
131
+ app.add_directive("story-list-for-persona", StoryListForPersonaDirective)
132
+ app.add_directive("story-list-for-app", StoryListForAppDirective)
133
+ app.add_directive("story-index", StoryIndexDirective)
134
+ app.add_directive("story-app", StoryAppDirective)
135
+ app.add_node(StorySeeAlsoPlaceholder)
136
+
137
+ # Register deprecated story aliases
138
+ app.add_directive("gherkin-story", GherkinStoryDirective)
139
+ app.add_directive("gherkin-stories", GherkinStoriesDirective)
140
+ app.add_directive("gherkin-stories-for-persona", GherkinStoriesForPersonaDirective)
141
+ app.add_directive("gherkin-stories-for-app", GherkinStoriesForAppDirective)
142
+ app.add_directive("gherkin-stories-index", GherkinStoriesIndexDirective)
143
+ app.add_directive("gherkin-app-stories", GherkinAppStoriesDirective)
144
+
145
+ # Register journey directives
146
+ app.add_directive("define-journey", DefineJourneyDirective)
147
+ app.add_directive("step-story", StepStoryDirective)
148
+ app.add_directive("step-epic", StepEpicDirective)
149
+ app.add_directive("step-phase", StepPhaseDirective)
150
+ app.add_directive("journey-index", JourneyIndexDirective)
151
+ app.add_directive("journey-dependency-graph", JourneyDependencyGraphDirective)
152
+ app.add_directive("journeys-for-persona", JourneysForPersonaDirective)
153
+ app.add_node(JourneyDependencyGraphPlaceholder)
154
+
155
+ # Register epic directives
156
+ app.add_directive("define-epic", DefineEpicDirective)
157
+ app.add_directive("epic-story", EpicStoryDirective)
158
+ app.add_directive("epic-index", EpicIndexDirective)
159
+ app.add_directive("epics-for-persona", EpicsForPersonaDirective)
160
+ app.add_node(EpicIndexPlaceholder)
161
+ app.add_node(EpicsForPersonaPlaceholder)
162
+
163
+ # Register app directives
164
+ app.add_directive("define-app", DefineAppDirective)
165
+ app.add_directive("app-index", AppIndexDirective)
166
+ app.add_directive("apps-for-persona", AppsForPersonaDirective)
167
+ app.add_node(DefineAppPlaceholder)
168
+ app.add_node(AppIndexPlaceholder)
169
+ app.add_node(AppsForPersonaPlaceholder)
170
+
171
+ # Register accelerator directives
172
+ app.add_directive("define-accelerator", DefineAcceleratorDirective)
173
+ app.add_directive("accelerator-index", AcceleratorIndexDirective)
174
+ app.add_directive("accelerators-for-app", AcceleratorsForAppDirective)
175
+ app.add_directive("dependent-accelerators", DependentAcceleratorsDirective)
176
+ app.add_directive(
177
+ "accelerator-dependency-diagram", AcceleratorDependencyDiagramDirective
178
+ )
179
+ app.add_directive("accelerator-status", AcceleratorStatusDirective)
180
+ app.add_node(DefineAcceleratorPlaceholder)
181
+ app.add_node(AcceleratorIndexPlaceholder)
182
+ app.add_node(AcceleratorsForAppPlaceholder)
183
+ app.add_node(DependentAcceleratorsPlaceholder)
184
+ app.add_node(AcceleratorDependencyDiagramPlaceholder)
185
+
186
+ # Register integration directives
187
+ app.add_directive("define-integration", DefineIntegrationDirective)
188
+ app.add_directive("integration-index", IntegrationIndexDirective)
189
+ app.add_node(DefineIntegrationPlaceholder)
190
+ app.add_node(IntegrationIndexPlaceholder)
191
+
192
+ # Register persona directives
193
+ app.add_directive("persona-diagram", PersonaDiagramDirective)
194
+ app.add_directive("persona-index-diagram", PersonaIndexDiagramDirective)
195
+ app.add_node(PersonaDiagramPlaceholder)
196
+ app.add_node(PersonaIndexDiagramPlaceholder)
197
+
198
+ logger.info("Loaded julee_viewpoints.sphinx_hcd extensions")
199
+
200
+ return {
201
+ "version": "2.0",
202
+ "parallel_read_safe": False,
203
+ "parallel_write_safe": True,
204
+ }
205
+
206
+
207
+ def _init_config_handler(app):
208
+ """Initialize HCD config from Sphinx app config."""
209
+ init_config(app)
@@ -0,0 +1,149 @@
1
+ """Configuration for sphinx_hcd extension.
2
+
3
+ Provides defaults matching the RBA solution layout, with ability to override
4
+ via sphinx_hcd config dict in conf.py.
5
+ """
6
+
7
+ from copy import deepcopy
8
+ from pathlib import Path
9
+
10
+ DEFAULT_CONFIG = {
11
+ "paths": {
12
+ # Where to find Gherkin feature files: {app}/features/*.feature
13
+ "feature_files": "tests/e2e/",
14
+ # Where to find app manifests: */app.yaml
15
+ "app_manifests": "apps/",
16
+ # Where to find integration manifests: */integration.yaml
17
+ "integration_manifests": "src/integrations/",
18
+ # Where to find bounded context code: {slug}/ directories
19
+ "bounded_contexts": "src/",
20
+ },
21
+ "docs_structure": {
22
+ # RST file locations relative to docs root
23
+ "applications": "applications",
24
+ "personas": "users/personas",
25
+ "journeys": "users/journeys",
26
+ "epics": "users/epics",
27
+ "accelerators": "domain/accelerators",
28
+ "integrations": "integrations",
29
+ "stories": "users/stories",
30
+ },
31
+ }
32
+
33
+
34
+ def config_factory() -> dict:
35
+ """Return a fresh config dict populated with defaults.
36
+
37
+ Usage in conf.py::
38
+
39
+ from julee_viewpoints.sphinx_hcd import config_factory
40
+
41
+ sphinx_hcd = config_factory()
42
+ sphinx_hcd['paths']['feature_files'] = 'tests/bdd/'
43
+
44
+ Returns:
45
+ A deep copy of DEFAULT_CONFIG that can be modified.
46
+ """
47
+ return deepcopy(DEFAULT_CONFIG)
48
+
49
+
50
+ def _deep_merge(base: dict, override: dict) -> dict:
51
+ """Deep merge override into base, returning new dict."""
52
+ result = deepcopy(base)
53
+ for key, value in override.items():
54
+ if key in result and isinstance(result[key], dict) and isinstance(value, dict):
55
+ result[key] = _deep_merge(result[key], value)
56
+ else:
57
+ result[key] = deepcopy(value)
58
+ return result
59
+
60
+
61
+ class HCDConfig:
62
+ """Configuration holder for sphinx_hcd extension.
63
+
64
+ Provides access to paths and doc structure settings, resolving paths
65
+ relative to the project root.
66
+ """
67
+
68
+ def __init__(self, app):
69
+ """Initialize config from Sphinx app.
70
+
71
+ Args:
72
+ app: Sphinx application instance
73
+ """
74
+ self._app = app
75
+ self._docs_dir = Path(app.srcdir)
76
+ self._project_root = self._docs_dir.parent
77
+
78
+ # Merge user config with defaults
79
+ user_config = getattr(app.config, "sphinx_hcd", {}) or {}
80
+ self._config = _deep_merge(DEFAULT_CONFIG, user_config)
81
+
82
+ @property
83
+ def project_root(self) -> Path:
84
+ """Project root directory (parent of docs/)."""
85
+ return self._project_root
86
+
87
+ @property
88
+ def docs_dir(self) -> Path:
89
+ """Documentation source directory."""
90
+ return self._docs_dir
91
+
92
+ def get_path(self, key: str) -> Path:
93
+ """Get an absolute path from the paths config.
94
+
95
+ Args:
96
+ key: Path key (e.g., 'feature_files', 'app_manifests')
97
+
98
+ Returns:
99
+ Absolute Path resolved relative to project root
100
+ """
101
+ rel_path: str = self._config["paths"].get(key, "")
102
+ return self._project_root / rel_path
103
+
104
+ def get_doc_path(self, key: str) -> str:
105
+ """Get a doc structure path.
106
+
107
+ Args:
108
+ key: Doc path key (e.g., 'applications', 'personas')
109
+
110
+ Returns:
111
+ Relative path string for use in doc references
112
+ """
113
+ doc_path: str = self._config["docs_structure"].get(key, key)
114
+ return doc_path
115
+
116
+
117
+ # Module-level config instance, set by setup()
118
+ _config: HCDConfig | None = None
119
+
120
+
121
+ def get_config() -> HCDConfig:
122
+ """Get the current HCD configuration.
123
+
124
+ Returns:
125
+ HCDConfig instance
126
+
127
+ Raises:
128
+ RuntimeError: If called before extension is initialized
129
+ """
130
+ if _config is None:
131
+ raise RuntimeError(
132
+ "sphinx_hcd config not initialized. "
133
+ "Ensure 'julee_viewpoints.sphinx_hcd' is in your Sphinx extensions."
134
+ )
135
+ return _config
136
+
137
+
138
+ def init_config(app) -> HCDConfig:
139
+ """Initialize config from Sphinx app. Called by extension setup.
140
+
141
+ Args:
142
+ app: Sphinx application instance
143
+
144
+ Returns:
145
+ HCDConfig instance
146
+ """
147
+ global _config
148
+ _config = HCDConfig(app)
149
+ return _config
@@ -0,0 +1,5 @@
1
+ """Domain layer for sphinx_hcd.
2
+
3
+ Contains domain models, repository protocols, and use cases following
4
+ julee clean architecture patterns.
5
+ """
@@ -0,0 +1,32 @@
1
+ """Domain models for sphinx_hcd.
2
+
3
+ Pydantic models representing HCD entities: stories, journeys, epics,
4
+ apps, accelerators, integrations, and personas.
5
+ """
6
+
7
+ from .accelerator import Accelerator, IntegrationReference
8
+ from .app import App, AppType
9
+ from .code_info import BoundedContextInfo, ClassInfo
10
+ from .epic import Epic
11
+ from .integration import Direction, ExternalDependency, Integration
12
+ from .journey import Journey, JourneyStep, StepType
13
+ from .persona import Persona
14
+ from .story import Story
15
+
16
+ __all__ = [
17
+ "Accelerator",
18
+ "App",
19
+ "AppType",
20
+ "BoundedContextInfo",
21
+ "ClassInfo",
22
+ "Direction",
23
+ "Epic",
24
+ "ExternalDependency",
25
+ "Integration",
26
+ "IntegrationReference",
27
+ "Journey",
28
+ "JourneyStep",
29
+ "Persona",
30
+ "StepType",
31
+ "Story",
32
+ ]
@@ -0,0 +1,158 @@
1
+ """Accelerator domain model.
2
+
3
+ Represents an accelerator (bounded context) in the HCD documentation system.
4
+ Accelerators are defined via RST directives and may have associated code.
5
+ """
6
+
7
+ from pydantic import Field, field_validator
8
+
9
+ from julee.core.entities.entity import Entity
10
+
11
+
12
+ class IntegrationReference(Entity):
13
+ """Reference to an integration with optional description.
14
+
15
+ Used for sources_from and publishes_to relationships where
16
+ an accelerator may specify what data it sources or publishes.
17
+ """
18
+
19
+ slug: str = Field(description='Integration slug (e.g., "pilot-data-collection")')
20
+ description: str = Field(
21
+ default="",
22
+ description='What is sourced/published (e.g., "Scheme documentation")',
23
+ )
24
+
25
+ @field_validator("slug", mode="before")
26
+ @classmethod
27
+ def validate_slug(cls, v: str) -> str:
28
+ """Validate slug is not empty."""
29
+ if not v or not v.strip():
30
+ raise ValueError("slug cannot be empty")
31
+ return v.strip()
32
+
33
+ @classmethod
34
+ def from_dict(cls, data: dict | str) -> "IntegrationReference":
35
+ """Create from dict or string.
36
+
37
+ Args:
38
+ data: Either a dict with slug/description or a plain string slug
39
+
40
+ Returns:
41
+ IntegrationReference instance
42
+ """
43
+ if isinstance(data, str):
44
+ return cls(slug=data)
45
+ return cls(slug=data.get("slug", ""), description=data.get("description", ""))
46
+
47
+
48
+ class Accelerator(Entity):
49
+ """Accelerator entity.
50
+
51
+ An accelerator represents a bounded context that provides business
52
+ capabilities. It may have associated code in src/{slug}/ and is
53
+ exposed through one or more applications.
54
+ """
55
+
56
+ slug: str = Field(description='URL-safe identifier (e.g., "vocabulary")')
57
+ status: str = Field(
58
+ default="",
59
+ description='Development status (e.g., "alpha", "production", "future")',
60
+ )
61
+ milestone: str | None = Field(
62
+ default=None, description='Target milestone (e.g., "2 (Nov 2025)")'
63
+ )
64
+ acceptance: str | None = Field(
65
+ default=None, description="Acceptance criteria description"
66
+ )
67
+ objective: str = Field(default="", description="Business objective/description")
68
+ sources_from: tuple[IntegrationReference, ...] = Field(
69
+ default_factory=tuple, description="Integrations this accelerator reads from"
70
+ )
71
+ feeds_into: tuple[str, ...] = Field(
72
+ default_factory=tuple, description="Other accelerators this one feeds data into"
73
+ )
74
+ publishes_to: tuple[IntegrationReference, ...] = Field(
75
+ default_factory=tuple, description="Integrations this accelerator writes to"
76
+ )
77
+ depends_on: tuple[str, ...] = Field(
78
+ default_factory=tuple, description="Other accelerators this one depends on"
79
+ )
80
+ docname: str = Field(
81
+ default="", description="RST document name (for incremental builds)"
82
+ )
83
+
84
+ @field_validator("slug", mode="before")
85
+ @classmethod
86
+ def validate_slug(cls, v: str) -> str:
87
+ """Validate slug is not empty."""
88
+ if not v or not v.strip():
89
+ raise ValueError("slug cannot be empty")
90
+ return v.strip()
91
+
92
+ @property
93
+ def display_title(self) -> str:
94
+ """Get formatted title for display."""
95
+ return self.slug.replace("-", " ").title()
96
+
97
+ @property
98
+ def status_normalized(self) -> str:
99
+ """Get normalized status for grouping."""
100
+ return self.status.lower().strip() if self.status else ""
101
+
102
+ def has_integration_dependency(self, integration_slug: str) -> bool:
103
+ """Check if accelerator depends on an integration.
104
+
105
+ Args:
106
+ integration_slug: Integration slug to check
107
+
108
+ Returns:
109
+ True if sources_from or publishes_to contains this integration
110
+ """
111
+ for ref in self.sources_from:
112
+ if ref.slug == integration_slug:
113
+ return True
114
+ for ref in self.publishes_to:
115
+ if ref.slug == integration_slug:
116
+ return True
117
+ return False
118
+
119
+ def has_accelerator_dependency(self, accelerator_slug: str) -> bool:
120
+ """Check if accelerator depends on another accelerator.
121
+
122
+ Args:
123
+ accelerator_slug: Accelerator slug to check
124
+
125
+ Returns:
126
+ True if depends_on or feeds_into contains this accelerator
127
+ """
128
+ return (
129
+ accelerator_slug in self.depends_on or accelerator_slug in self.feeds_into
130
+ )
131
+
132
+ def get_sources_from_slugs(self) -> list[str]:
133
+ """Get list of integration slugs this accelerator sources from."""
134
+ return [ref.slug for ref in self.sources_from]
135
+
136
+ def get_publishes_to_slugs(self) -> list[str]:
137
+ """Get list of integration slugs this accelerator publishes to."""
138
+ return [ref.slug for ref in self.publishes_to]
139
+
140
+ def get_integration_description(
141
+ self, integration_slug: str, relationship: str
142
+ ) -> str | None:
143
+ """Get description for an integration relationship.
144
+
145
+ Args:
146
+ integration_slug: Integration to look up
147
+ relationship: Either "sources_from" or "publishes_to"
148
+
149
+ Returns:
150
+ Description if found, None otherwise
151
+ """
152
+ refs = (
153
+ self.sources_from if relationship == "sources_from" else self.publishes_to
154
+ )
155
+ for ref in refs:
156
+ if ref.slug == integration_slug:
157
+ return ref.description or None
158
+ return None