julee 0.1.5__py3-none-any.whl → 0.1.7__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 (108) hide show
  1. julee/__init__.py +1 -1
  2. julee/contrib/polling/apps/worker/pipelines.py +3 -1
  3. julee/contrib/polling/tests/unit/apps/worker/test_pipelines.py +3 -0
  4. julee/docs/sphinx_hcd/__init__.py +146 -13
  5. julee/docs/sphinx_hcd/domain/__init__.py +5 -0
  6. julee/docs/sphinx_hcd/domain/models/__init__.py +32 -0
  7. julee/docs/sphinx_hcd/domain/models/accelerator.py +152 -0
  8. julee/docs/sphinx_hcd/domain/models/app.py +151 -0
  9. julee/docs/sphinx_hcd/domain/models/code_info.py +121 -0
  10. julee/docs/sphinx_hcd/domain/models/epic.py +79 -0
  11. julee/docs/sphinx_hcd/domain/models/integration.py +230 -0
  12. julee/docs/sphinx_hcd/domain/models/journey.py +222 -0
  13. julee/docs/sphinx_hcd/domain/models/persona.py +106 -0
  14. julee/docs/sphinx_hcd/domain/models/story.py +128 -0
  15. julee/docs/sphinx_hcd/domain/repositories/__init__.py +25 -0
  16. julee/docs/sphinx_hcd/domain/repositories/accelerator.py +98 -0
  17. julee/docs/sphinx_hcd/domain/repositories/app.py +57 -0
  18. julee/docs/sphinx_hcd/domain/repositories/base.py +89 -0
  19. julee/docs/sphinx_hcd/domain/repositories/code_info.py +69 -0
  20. julee/docs/sphinx_hcd/domain/repositories/epic.py +62 -0
  21. julee/docs/sphinx_hcd/domain/repositories/integration.py +79 -0
  22. julee/docs/sphinx_hcd/domain/repositories/journey.py +106 -0
  23. julee/docs/sphinx_hcd/domain/repositories/story.py +68 -0
  24. julee/docs/sphinx_hcd/domain/use_cases/__init__.py +64 -0
  25. julee/docs/sphinx_hcd/domain/use_cases/derive_personas.py +166 -0
  26. julee/docs/sphinx_hcd/domain/use_cases/resolve_accelerator_references.py +236 -0
  27. julee/docs/sphinx_hcd/domain/use_cases/resolve_app_references.py +144 -0
  28. julee/docs/sphinx_hcd/domain/use_cases/resolve_story_references.py +121 -0
  29. julee/docs/sphinx_hcd/parsers/__init__.py +48 -0
  30. julee/docs/sphinx_hcd/parsers/ast.py +150 -0
  31. julee/docs/sphinx_hcd/parsers/gherkin.py +155 -0
  32. julee/docs/sphinx_hcd/parsers/yaml.py +184 -0
  33. julee/docs/sphinx_hcd/repositories/__init__.py +4 -0
  34. julee/docs/sphinx_hcd/repositories/memory/__init__.py +25 -0
  35. julee/docs/sphinx_hcd/repositories/memory/accelerator.py +86 -0
  36. julee/docs/sphinx_hcd/repositories/memory/app.py +45 -0
  37. julee/docs/sphinx_hcd/repositories/memory/base.py +106 -0
  38. julee/docs/sphinx_hcd/repositories/memory/code_info.py +59 -0
  39. julee/docs/sphinx_hcd/repositories/memory/epic.py +54 -0
  40. julee/docs/sphinx_hcd/repositories/memory/integration.py +70 -0
  41. julee/docs/sphinx_hcd/repositories/memory/journey.py +96 -0
  42. julee/docs/sphinx_hcd/repositories/memory/story.py +63 -0
  43. julee/docs/sphinx_hcd/sphinx/__init__.py +28 -0
  44. julee/docs/sphinx_hcd/sphinx/adapters.py +116 -0
  45. julee/docs/sphinx_hcd/sphinx/context.py +163 -0
  46. julee/docs/sphinx_hcd/sphinx/directives/__init__.py +160 -0
  47. julee/docs/sphinx_hcd/sphinx/directives/accelerator.py +576 -0
  48. julee/docs/sphinx_hcd/sphinx/directives/app.py +349 -0
  49. julee/docs/sphinx_hcd/sphinx/directives/base.py +211 -0
  50. julee/docs/sphinx_hcd/sphinx/directives/epic.py +434 -0
  51. julee/docs/sphinx_hcd/sphinx/directives/integration.py +220 -0
  52. julee/docs/sphinx_hcd/sphinx/directives/journey.py +642 -0
  53. julee/docs/sphinx_hcd/sphinx/directives/persona.py +345 -0
  54. julee/docs/sphinx_hcd/sphinx/directives/story.py +575 -0
  55. julee/docs/sphinx_hcd/sphinx/event_handlers/__init__.py +16 -0
  56. julee/docs/sphinx_hcd/sphinx/event_handlers/builder_inited.py +31 -0
  57. julee/docs/sphinx_hcd/sphinx/event_handlers/doctree_read.py +27 -0
  58. julee/docs/sphinx_hcd/sphinx/event_handlers/doctree_resolved.py +43 -0
  59. julee/docs/sphinx_hcd/sphinx/event_handlers/env_purge_doc.py +42 -0
  60. julee/docs/sphinx_hcd/sphinx/initialization.py +139 -0
  61. julee/docs/sphinx_hcd/tests/__init__.py +9 -0
  62. julee/docs/sphinx_hcd/tests/conftest.py +6 -0
  63. julee/docs/sphinx_hcd/tests/domain/__init__.py +1 -0
  64. julee/docs/sphinx_hcd/tests/domain/models/__init__.py +1 -0
  65. julee/docs/sphinx_hcd/tests/domain/models/test_accelerator.py +266 -0
  66. julee/docs/sphinx_hcd/tests/domain/models/test_app.py +258 -0
  67. julee/docs/sphinx_hcd/tests/domain/models/test_code_info.py +231 -0
  68. julee/docs/sphinx_hcd/tests/domain/models/test_epic.py +163 -0
  69. julee/docs/sphinx_hcd/tests/domain/models/test_integration.py +327 -0
  70. julee/docs/sphinx_hcd/tests/domain/models/test_journey.py +249 -0
  71. julee/docs/sphinx_hcd/tests/domain/models/test_persona.py +172 -0
  72. julee/docs/sphinx_hcd/tests/domain/models/test_story.py +216 -0
  73. julee/docs/sphinx_hcd/tests/domain/use_cases/__init__.py +1 -0
  74. julee/docs/sphinx_hcd/tests/domain/use_cases/test_derive_personas.py +314 -0
  75. julee/docs/sphinx_hcd/tests/domain/use_cases/test_resolve_accelerator_references.py +476 -0
  76. julee/docs/sphinx_hcd/tests/domain/use_cases/test_resolve_app_references.py +265 -0
  77. julee/docs/sphinx_hcd/tests/domain/use_cases/test_resolve_story_references.py +229 -0
  78. julee/docs/sphinx_hcd/tests/integration/__init__.py +1 -0
  79. julee/docs/sphinx_hcd/tests/parsers/__init__.py +1 -0
  80. julee/docs/sphinx_hcd/tests/parsers/test_ast.py +298 -0
  81. julee/docs/sphinx_hcd/tests/parsers/test_gherkin.py +282 -0
  82. julee/docs/sphinx_hcd/tests/parsers/test_yaml.py +496 -0
  83. julee/docs/sphinx_hcd/tests/repositories/__init__.py +1 -0
  84. julee/docs/sphinx_hcd/tests/repositories/test_accelerator.py +298 -0
  85. julee/docs/sphinx_hcd/tests/repositories/test_app.py +218 -0
  86. julee/docs/sphinx_hcd/tests/repositories/test_base.py +151 -0
  87. julee/docs/sphinx_hcd/tests/repositories/test_code_info.py +253 -0
  88. julee/docs/sphinx_hcd/tests/repositories/test_epic.py +237 -0
  89. julee/docs/sphinx_hcd/tests/repositories/test_integration.py +268 -0
  90. julee/docs/sphinx_hcd/tests/repositories/test_journey.py +294 -0
  91. julee/docs/sphinx_hcd/tests/repositories/test_story.py +236 -0
  92. julee/docs/sphinx_hcd/tests/sphinx/__init__.py +1 -0
  93. julee/docs/sphinx_hcd/tests/sphinx/directives/__init__.py +1 -0
  94. julee/docs/sphinx_hcd/tests/sphinx/directives/test_base.py +160 -0
  95. julee/docs/sphinx_hcd/tests/sphinx/test_adapters.py +176 -0
  96. julee/docs/sphinx_hcd/tests/sphinx/test_context.py +257 -0
  97. {julee-0.1.5.dist-info → julee-0.1.7.dist-info}/METADATA +2 -1
  98. {julee-0.1.5.dist-info → julee-0.1.7.dist-info}/RECORD +101 -16
  99. julee/docs/sphinx_hcd/accelerators.py +0 -1175
  100. julee/docs/sphinx_hcd/apps.py +0 -518
  101. julee/docs/sphinx_hcd/epics.py +0 -453
  102. julee/docs/sphinx_hcd/integrations.py +0 -310
  103. julee/docs/sphinx_hcd/journeys.py +0 -797
  104. julee/docs/sphinx_hcd/personas.py +0 -457
  105. julee/docs/sphinx_hcd/stories.py +0 -960
  106. {julee-0.1.5.dist-info → julee-0.1.7.dist-info}/WHEEL +0 -0
  107. {julee-0.1.5.dist-info → julee-0.1.7.dist-info}/licenses/LICENSE +0 -0
  108. {julee-0.1.5.dist-info → julee-0.1.7.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,106 @@
1
+ """JourneyRepository protocol.
2
+
3
+ Defines the interface for journey data access.
4
+ """
5
+
6
+ from typing import Protocol, runtime_checkable
7
+
8
+ from ..models.journey import Journey
9
+ from .base import BaseRepository
10
+
11
+
12
+ @runtime_checkable
13
+ class JourneyRepository(BaseRepository[Journey], Protocol):
14
+ """Repository protocol for Journey entities.
15
+
16
+ Extends BaseRepository with journey-specific query methods.
17
+ Journeys are defined in RST documents and support incremental builds
18
+ via docname tracking.
19
+ """
20
+
21
+ async def get_by_persona(self, persona: str) -> list[Journey]:
22
+ """Get all journeys for a persona.
23
+
24
+ Args:
25
+ persona: Persona name (case-insensitive matching)
26
+
27
+ Returns:
28
+ List of journeys where the persona matches
29
+ """
30
+ ...
31
+
32
+ async def get_by_docname(self, docname: str) -> list[Journey]:
33
+ """Get all journeys defined in a specific document.
34
+
35
+ Args:
36
+ docname: RST document name
37
+
38
+ Returns:
39
+ List of journeys from that document
40
+ """
41
+ ...
42
+
43
+ async def clear_by_docname(self, docname: str) -> int:
44
+ """Remove all journeys 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 journeys removed
53
+ """
54
+ ...
55
+
56
+ async def get_dependents(self, journey_slug: str) -> list[Journey]:
57
+ """Get journeys that depend on a specific journey.
58
+
59
+ Args:
60
+ journey_slug: Slug of the journey to find dependents of
61
+
62
+ Returns:
63
+ List of journeys that have this journey in depends_on
64
+ """
65
+ ...
66
+
67
+ async def get_dependencies(self, journey_slug: str) -> list[Journey]:
68
+ """Get journeys that a specific journey depends on.
69
+
70
+ Args:
71
+ journey_slug: Slug of the journey to find dependencies of
72
+
73
+ Returns:
74
+ List of journeys that this journey depends on
75
+ """
76
+ ...
77
+
78
+ async def get_all_personas(self) -> set[str]:
79
+ """Get all unique personas across all journeys.
80
+
81
+ Returns:
82
+ Set of persona names (normalized)
83
+ """
84
+ ...
85
+
86
+ async def get_with_story_ref(self, story_title: str) -> list[Journey]:
87
+ """Get journeys that reference a specific story.
88
+
89
+ Args:
90
+ story_title: Story feature title (case-insensitive)
91
+
92
+ Returns:
93
+ List of journeys containing this story in steps
94
+ """
95
+ ...
96
+
97
+ async def get_with_epic_ref(self, epic_slug: str) -> list[Journey]:
98
+ """Get journeys that reference a specific epic.
99
+
100
+ Args:
101
+ epic_slug: Epic slug
102
+
103
+ Returns:
104
+ List of journeys containing this epic in steps
105
+ """
106
+ ...
@@ -0,0 +1,68 @@
1
+ """StoryRepository protocol.
2
+
3
+ Defines the interface for story data access.
4
+ """
5
+
6
+ from typing import Protocol, runtime_checkable
7
+
8
+ from ..models.story import Story
9
+ from .base import BaseRepository
10
+
11
+
12
+ @runtime_checkable
13
+ class StoryRepository(BaseRepository[Story], Protocol):
14
+ """Repository protocol for Story entities.
15
+
16
+ Extends BaseRepository with story-specific query methods.
17
+ Stories are indexed from .feature files and are read-only during
18
+ a Sphinx build (populated at builder-inited, queried during rendering).
19
+ """
20
+
21
+ async def get_by_app(self, app_slug: str) -> list[Story]:
22
+ """Get all stories for an application.
23
+
24
+ Args:
25
+ app_slug: Application slug (e.g., "staff-portal")
26
+
27
+ Returns:
28
+ List of stories belonging to the app
29
+ """
30
+ ...
31
+
32
+ async def get_by_persona(self, persona: str) -> list[Story]:
33
+ """Get all stories for a persona.
34
+
35
+ Args:
36
+ persona: Persona name (case-insensitive matching)
37
+
38
+ Returns:
39
+ List of stories where the persona matches
40
+ """
41
+ ...
42
+
43
+ async def get_by_feature_title(self, feature_title: str) -> Story | None:
44
+ """Get a story by its feature title.
45
+
46
+ Args:
47
+ feature_title: The Feature: line content (case-insensitive)
48
+
49
+ Returns:
50
+ Story if found, None otherwise
51
+ """
52
+ ...
53
+
54
+ async def get_apps_with_stories(self) -> set[str]:
55
+ """Get the set of app slugs that have stories.
56
+
57
+ Returns:
58
+ Set of app slugs (normalized)
59
+ """
60
+ ...
61
+
62
+ async def get_all_personas(self) -> set[str]:
63
+ """Get all unique personas across all stories.
64
+
65
+ Returns:
66
+ Set of persona names (normalized)
67
+ """
68
+ ...
@@ -0,0 +1,64 @@
1
+ """Use cases for sphinx_hcd.
2
+
3
+ Business logic for cross-referencing and deriving entities.
4
+ """
5
+
6
+ from .derive_personas import (
7
+ derive_personas,
8
+ derive_personas_by_app_type,
9
+ get_apps_for_persona,
10
+ get_epics_for_persona,
11
+ )
12
+ from .resolve_accelerator_references import (
13
+ get_accelerator_cross_references,
14
+ get_apps_for_accelerator,
15
+ get_code_info_for_accelerator,
16
+ get_dependent_accelerators,
17
+ get_fed_by_accelerators,
18
+ get_journeys_for_accelerator,
19
+ get_publish_integrations,
20
+ get_source_integrations,
21
+ get_stories_for_accelerator,
22
+ )
23
+ from .resolve_app_references import (
24
+ get_app_cross_references,
25
+ get_epics_for_app,
26
+ get_journeys_for_app,
27
+ get_personas_for_app,
28
+ get_stories_for_app,
29
+ )
30
+ from .resolve_story_references import (
31
+ get_epics_for_story,
32
+ get_journeys_for_story,
33
+ get_related_stories,
34
+ get_story_cross_references,
35
+ )
36
+
37
+ __all__ = [
38
+ # Persona derivation
39
+ "derive_personas",
40
+ "derive_personas_by_app_type",
41
+ "get_apps_for_persona",
42
+ "get_epics_for_persona",
43
+ # Story references
44
+ "get_epics_for_story",
45
+ "get_journeys_for_story",
46
+ "get_related_stories",
47
+ "get_story_cross_references",
48
+ # App references
49
+ "get_app_cross_references",
50
+ "get_epics_for_app",
51
+ "get_journeys_for_app",
52
+ "get_personas_for_app",
53
+ "get_stories_for_app",
54
+ # Accelerator references
55
+ "get_accelerator_cross_references",
56
+ "get_apps_for_accelerator",
57
+ "get_code_info_for_accelerator",
58
+ "get_dependent_accelerators",
59
+ "get_fed_by_accelerators",
60
+ "get_journeys_for_accelerator",
61
+ "get_publish_integrations",
62
+ "get_source_integrations",
63
+ "get_stories_for_accelerator",
64
+ ]
@@ -0,0 +1,166 @@
1
+ """Use case for deriving personas from stories and epics.
2
+
3
+ Personas are not defined directly but are extracted from user stories.
4
+ This use case collects persona information from stories and enriches
5
+ it with epic participation data.
6
+ """
7
+
8
+ from collections import defaultdict
9
+
10
+ from ...utils import normalize_name
11
+ from ..models.app import App
12
+ from ..models.epic import Epic
13
+ from ..models.persona import Persona
14
+ from ..models.story import Story
15
+
16
+
17
+ def derive_personas(
18
+ stories: list[Story],
19
+ epics: list[Epic],
20
+ ) -> list[Persona]:
21
+ """Derive personas from stories and epics.
22
+
23
+ Extracts unique personas from stories, then enriches with:
24
+ - List of apps each persona uses (from stories)
25
+ - List of epics each persona participates in (stories in epics)
26
+
27
+ Args:
28
+ stories: List of Story entities
29
+ epics: List of Epic entities
30
+
31
+ Returns:
32
+ List of Persona entities sorted by name
33
+ """
34
+ # Collect persona data from stories
35
+ persona_data: dict[str, dict] = {} # normalized_name -> {name, apps}
36
+
37
+ for story in stories:
38
+ normalized = story.persona_normalized
39
+ if normalized == "unknown":
40
+ continue
41
+
42
+ if normalized not in persona_data:
43
+ persona_data[normalized] = {
44
+ "name": story.persona,
45
+ "apps": set(),
46
+ "epics": set(),
47
+ }
48
+
49
+ persona_data[normalized]["apps"].add(story.app_slug)
50
+
51
+ # Build lookup of normalized story title -> normalized persona
52
+ story_to_persona: dict[str, str] = {}
53
+ for story in stories:
54
+ story_to_persona[normalize_name(story.feature_title)] = story.persona_normalized
55
+
56
+ # Find epics for each persona
57
+ for epic in epics:
58
+ for story_ref in epic.story_refs:
59
+ story_normalized = normalize_name(story_ref)
60
+ persona_normalized = story_to_persona.get(story_normalized)
61
+ if persona_normalized and persona_normalized in persona_data:
62
+ persona_data[persona_normalized]["epics"].add(epic.slug)
63
+
64
+ # Build Persona entities
65
+ personas = []
66
+ for data in persona_data.values():
67
+ persona = Persona(
68
+ name=data["name"],
69
+ app_slugs=sorted(data["apps"]),
70
+ epic_slugs=sorted(data["epics"]),
71
+ )
72
+ personas.append(persona)
73
+
74
+ return sorted(personas, key=lambda p: p.name)
75
+
76
+
77
+ def derive_personas_by_app_type(
78
+ stories: list[Story],
79
+ epics: list[Epic],
80
+ apps: list[App],
81
+ ) -> dict[str, list[Persona]]:
82
+ """Derive personas grouped by the type of apps they use.
83
+
84
+ Args:
85
+ stories: List of Story entities
86
+ epics: List of Epic entities
87
+ apps: List of App entities
88
+
89
+ Returns:
90
+ Dict mapping app type strings to lists of Persona entities
91
+ """
92
+ # First derive all personas
93
+ all_personas = derive_personas(stories, epics)
94
+
95
+ # Build app slug -> app type lookup
96
+ app_types: dict[str, str] = {}
97
+ for app in apps:
98
+ app_types[app.slug] = app.app_type.value if app.app_type else "unknown"
99
+
100
+ # Group personas by app type
101
+ personas_by_type: dict[str, list[Persona]] = defaultdict(list)
102
+
103
+ for persona in all_personas:
104
+ # Find all app types this persona uses
105
+ persona_types: set[str] = set()
106
+ for app_slug in persona.app_slugs:
107
+ app_type = app_types.get(app_slug, "unknown")
108
+ persona_types.add(app_type)
109
+
110
+ # Add persona to each type group
111
+ for app_type in persona_types:
112
+ personas_by_type[app_type].append(persona)
113
+
114
+ # Sort personas within each group
115
+ return {
116
+ app_type: sorted(personas, key=lambda p: p.name)
117
+ for app_type, personas in personas_by_type.items()
118
+ }
119
+
120
+
121
+ def get_epics_for_persona(
122
+ persona: Persona,
123
+ epics: list[Epic],
124
+ stories: list[Story],
125
+ ) -> list[Epic]:
126
+ """Get Epic entities for a persona.
127
+
128
+ Args:
129
+ persona: Persona to get epics for
130
+ epics: All Epic entities
131
+ stories: All Story entities
132
+
133
+ Returns:
134
+ List of Epic entities containing stories for this persona
135
+ """
136
+ # Build lookup of normalized story title -> normalized persona
137
+ story_to_persona: dict[str, str] = {}
138
+ for story in stories:
139
+ story_to_persona[normalize_name(story.feature_title)] = story.persona_normalized
140
+
141
+ matching_epics = []
142
+ for epic in epics:
143
+ for story_ref in epic.story_refs:
144
+ story_normalized = normalize_name(story_ref)
145
+ if story_to_persona.get(story_normalized) == persona.normalized_name:
146
+ matching_epics.append(epic)
147
+ break
148
+
149
+ return sorted(matching_epics, key=lambda e: e.slug)
150
+
151
+
152
+ def get_apps_for_persona(
153
+ persona: Persona,
154
+ apps: list[App],
155
+ ) -> list[App]:
156
+ """Get App entities for a persona.
157
+
158
+ Args:
159
+ persona: Persona to get apps for
160
+ apps: All App entities
161
+
162
+ Returns:
163
+ List of App entities this persona uses
164
+ """
165
+ app_lookup = {app.slug: app for app in apps}
166
+ return [app_lookup[slug] for slug in persona.app_slugs if slug in app_lookup]
@@ -0,0 +1,236 @@
1
+ """Use case for resolving accelerator references.
2
+
3
+ Finds apps, stories, journeys, and integrations related to an accelerator.
4
+ """
5
+
6
+ from ...utils import normalize_name
7
+ from ..models.accelerator import Accelerator
8
+ from ..models.app import App
9
+ from ..models.code_info import BoundedContextInfo
10
+ from ..models.integration import Integration
11
+ from ..models.journey import Journey
12
+ from ..models.story import Story
13
+
14
+
15
+ def get_apps_for_accelerator(
16
+ accelerator: Accelerator,
17
+ apps: list[App],
18
+ ) -> list[App]:
19
+ """Get apps that expose an accelerator.
20
+
21
+ Apps expose accelerators via the 'accelerators' field in their manifest.
22
+
23
+ Args:
24
+ accelerator: Accelerator to find apps for
25
+ apps: All App entities
26
+
27
+ Returns:
28
+ List of App entities that expose this accelerator, sorted by slug
29
+ """
30
+ matching = [app for app in apps if accelerator.slug in (app.accelerators or [])]
31
+ return sorted(matching, key=lambda a: a.slug)
32
+
33
+
34
+ def get_stories_for_accelerator(
35
+ accelerator: Accelerator,
36
+ apps: list[App],
37
+ stories: list[Story],
38
+ ) -> list[Story]:
39
+ """Get stories for apps that expose an accelerator.
40
+
41
+ Args:
42
+ accelerator: Accelerator to find stories for
43
+ apps: All App entities
44
+ stories: All Story entities
45
+
46
+ Returns:
47
+ List of Story entities from apps that expose this accelerator
48
+ """
49
+ # Get app slugs that expose this accelerator
50
+ app_slugs = {
51
+ app.slug for app in apps if accelerator.slug in (app.accelerators or [])
52
+ }
53
+
54
+ if not app_slugs:
55
+ return []
56
+
57
+ # Find stories for those apps
58
+ matching = [s for s in stories if s.app_slug in app_slugs]
59
+ return sorted(matching, key=lambda s: s.feature_title)
60
+
61
+
62
+ def get_journeys_for_accelerator(
63
+ accelerator: Accelerator,
64
+ apps: list[App],
65
+ stories: list[Story],
66
+ journeys: list[Journey],
67
+ ) -> list[Journey]:
68
+ """Get journeys that include stories from an accelerator's apps.
69
+
70
+ Args:
71
+ accelerator: Accelerator to find journeys for
72
+ apps: All App entities
73
+ stories: All Story entities
74
+ journeys: All Journey entities
75
+
76
+ Returns:
77
+ List of Journey entities containing stories from this accelerator's apps
78
+ """
79
+ # Get stories for this accelerator
80
+ accel_stories = get_stories_for_accelerator(accelerator, apps, stories)
81
+ story_titles = {normalize_name(s.feature_title) for s in accel_stories}
82
+
83
+ if not story_titles:
84
+ return []
85
+
86
+ # Find journeys containing these stories
87
+ matching = []
88
+ for journey in journeys:
89
+ story_refs = journey.get_story_refs()
90
+ if any(normalize_name(ref) in story_titles for ref in story_refs):
91
+ matching.append(journey)
92
+
93
+ return sorted(matching, key=lambda j: j.slug)
94
+
95
+
96
+ def get_source_integrations(
97
+ accelerator: Accelerator,
98
+ integrations: list[Integration],
99
+ ) -> list[Integration]:
100
+ """Get integrations that an accelerator sources from.
101
+
102
+ Args:
103
+ accelerator: Accelerator to find sources for
104
+ integrations: All Integration entities
105
+
106
+ Returns:
107
+ List of Integration entities this accelerator sources from
108
+ """
109
+ source_slugs = accelerator.get_sources_from_slugs()
110
+ integration_lookup = {i.slug: i for i in integrations}
111
+
112
+ return [
113
+ integration_lookup[slug] for slug in source_slugs if slug in integration_lookup
114
+ ]
115
+
116
+
117
+ def get_publish_integrations(
118
+ accelerator: Accelerator,
119
+ integrations: list[Integration],
120
+ ) -> list[Integration]:
121
+ """Get integrations that an accelerator publishes to.
122
+
123
+ Args:
124
+ accelerator: Accelerator to find publish targets for
125
+ integrations: All Integration entities
126
+
127
+ Returns:
128
+ List of Integration entities this accelerator publishes to
129
+ """
130
+ publish_slugs = accelerator.get_publishes_to_slugs()
131
+ integration_lookup = {i.slug: i for i in integrations}
132
+
133
+ return [
134
+ integration_lookup[slug] for slug in publish_slugs if slug in integration_lookup
135
+ ]
136
+
137
+
138
+ def get_dependent_accelerators(
139
+ accelerator: Accelerator,
140
+ accelerators: list[Accelerator],
141
+ ) -> list[Accelerator]:
142
+ """Get accelerators that depend on a specific accelerator.
143
+
144
+ Args:
145
+ accelerator: Accelerator to find dependents of
146
+ accelerators: All Accelerator entities
147
+
148
+ Returns:
149
+ List of Accelerator entities that depend on this one
150
+ """
151
+ matching = [a for a in accelerators if accelerator.slug in a.depends_on]
152
+ return sorted(matching, key=lambda a: a.slug)
153
+
154
+
155
+ def get_fed_by_accelerators(
156
+ accelerator: Accelerator,
157
+ accelerators: list[Accelerator],
158
+ ) -> list[Accelerator]:
159
+ """Get accelerators that feed into a specific accelerator.
160
+
161
+ Args:
162
+ accelerator: Accelerator to find feeders for
163
+ accelerators: All Accelerator entities
164
+
165
+ Returns:
166
+ List of Accelerator entities that feed into this one
167
+ """
168
+ matching = [a for a in accelerators if accelerator.slug in a.feeds_into]
169
+ return sorted(matching, key=lambda a: a.slug)
170
+
171
+
172
+ def get_code_info_for_accelerator(
173
+ accelerator: Accelerator,
174
+ code_infos: list[BoundedContextInfo],
175
+ ) -> BoundedContextInfo | None:
176
+ """Get code info for an accelerator's bounded context.
177
+
178
+ Tries to match by slug or snake_case version of slug.
179
+
180
+ Args:
181
+ accelerator: Accelerator to find code for
182
+ code_infos: All BoundedContextInfo entities
183
+
184
+ Returns:
185
+ BoundedContextInfo if found, None otherwise
186
+ """
187
+ # Try exact match
188
+ for info in code_infos:
189
+ if info.slug == accelerator.slug:
190
+ return info
191
+
192
+ # Try snake_case match
193
+ snake_slug = accelerator.slug.replace("-", "_")
194
+ for info in code_infos:
195
+ if info.slug == snake_slug or info.code_dir == snake_slug:
196
+ return info
197
+
198
+ return None
199
+
200
+
201
+ def get_accelerator_cross_references(
202
+ accelerator: Accelerator,
203
+ accelerators: list[Accelerator],
204
+ apps: list[App],
205
+ stories: list[Story],
206
+ journeys: list[Journey],
207
+ integrations: list[Integration],
208
+ code_infos: list[BoundedContextInfo],
209
+ ) -> dict:
210
+ """Get all cross-references for an accelerator.
211
+
212
+ Convenience function to get all related entities at once.
213
+
214
+ Args:
215
+ accelerator: Accelerator to find references for
216
+ accelerators: All Accelerator entities
217
+ apps: All App entities
218
+ stories: All Story entities
219
+ journeys: All Journey entities
220
+ integrations: All Integration entities
221
+ code_infos: All BoundedContextInfo entities
222
+
223
+ Returns:
224
+ Dict with keys: apps, stories, journeys, source_integrations,
225
+ publish_integrations, dependents, fed_by, code_info
226
+ """
227
+ return {
228
+ "apps": get_apps_for_accelerator(accelerator, apps),
229
+ "stories": get_stories_for_accelerator(accelerator, apps, stories),
230
+ "journeys": get_journeys_for_accelerator(accelerator, apps, stories, journeys),
231
+ "source_integrations": get_source_integrations(accelerator, integrations),
232
+ "publish_integrations": get_publish_integrations(accelerator, integrations),
233
+ "dependents": get_dependent_accelerators(accelerator, accelerators),
234
+ "fed_by": get_fed_by_accelerators(accelerator, accelerators),
235
+ "code_info": get_code_info_for_accelerator(accelerator, code_infos),
236
+ }