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,576 @@
1
+ """Accelerator directives for sphinx_hcd.
2
+
3
+ Provides directives for accelerators with code introspection:
4
+ - define-accelerator: Define accelerator with metadata + introspected code
5
+ - accelerator-index: Generate index table grouped by status
6
+ - accelerators-for-app: List accelerators an app exposes
7
+ - dependent-accelerators: List accelerators that depend on an integration
8
+ - accelerator-dependency-diagram: Generate PlantUML component diagram
9
+ - accelerator-status: Show status, milestone, and acceptance info
10
+ """
11
+
12
+ import os
13
+
14
+ from docutils import nodes
15
+ from docutils.parsers.rst import directives
16
+
17
+ from ...domain.models.accelerator import Accelerator, IntegrationReference
18
+ from ...domain.use_cases import (
19
+ get_apps_for_accelerator,
20
+ get_code_info_for_accelerator,
21
+ get_fed_by_accelerators,
22
+ get_publish_integrations,
23
+ get_source_integrations,
24
+ )
25
+ from ...utils import (
26
+ parse_integration_options,
27
+ parse_list_option,
28
+ path_to_root,
29
+ )
30
+ from .base import HCDDirective
31
+
32
+
33
+ class DefineAcceleratorPlaceholder(nodes.General, nodes.Element):
34
+ """Placeholder for define-accelerator, replaced at doctree-resolved."""
35
+
36
+ pass
37
+
38
+
39
+ class AcceleratorIndexPlaceholder(nodes.General, nodes.Element):
40
+ """Placeholder for accelerator-index, replaced at doctree-resolved."""
41
+
42
+ pass
43
+
44
+
45
+ class AcceleratorsForAppPlaceholder(nodes.General, nodes.Element):
46
+ """Placeholder for accelerators-for-app, replaced at doctree-resolved."""
47
+
48
+ pass
49
+
50
+
51
+ class DependentAcceleratorsPlaceholder(nodes.General, nodes.Element):
52
+ """Placeholder for dependent-accelerators, replaced at doctree-resolved."""
53
+
54
+ pass
55
+
56
+
57
+ class AcceleratorDependencyDiagramPlaceholder(nodes.General, nodes.Element):
58
+ """Placeholder for accelerator-dependency-diagram, replaced at doctree-resolved."""
59
+
60
+ pass
61
+
62
+
63
+ class DefineAcceleratorDirective(HCDDirective):
64
+ """Define an accelerator with metadata and introspected code.
65
+
66
+ Usage::
67
+
68
+ .. define-accelerator:: vocabulary-catalog
69
+ :status: active
70
+ :milestone: MVP
71
+ :acceptance: All vocab terms published to CDC
72
+ :sources-from: kafka
73
+ :publishes-to: elasticsearch
74
+ :depends-on: document-processor
75
+ :feeds-into: compliance-mapper
76
+ """
77
+
78
+ required_arguments = 1
79
+ has_content = True
80
+ option_spec = {
81
+ "status": directives.unchanged,
82
+ "milestone": directives.unchanged,
83
+ "acceptance": directives.unchanged,
84
+ "sources-from": directives.unchanged,
85
+ "publishes-to": directives.unchanged,
86
+ "depends-on": directives.unchanged,
87
+ "feeds-into": directives.unchanged,
88
+ }
89
+
90
+ def run(self):
91
+ slug = self.arguments[0]
92
+ docname = self.env.docname
93
+
94
+ # Parse options
95
+ status = self.options.get("status", "").strip()
96
+ milestone = self.options.get("milestone", "").strip() or None
97
+ acceptance = self.options.get("acceptance", "").strip() or None
98
+ sources_from = parse_integration_options(self.options.get("sources-from", ""))
99
+ publishes_to = parse_integration_options(self.options.get("publishes-to", ""))
100
+ depends_on = parse_list_option(self.options.get("depends-on", ""))
101
+ feeds_into = parse_list_option(self.options.get("feeds-into", ""))
102
+ objective = "\n".join(self.content).strip()
103
+
104
+ # Create accelerator entity
105
+ accelerator = Accelerator(
106
+ slug=slug,
107
+ status=status,
108
+ milestone=milestone,
109
+ acceptance=acceptance,
110
+ objective=objective,
111
+ sources_from=[
112
+ IntegrationReference(
113
+ slug=s["slug"], description=s.get("description", "")
114
+ )
115
+ for s in sources_from
116
+ ],
117
+ publishes_to=[
118
+ IntegrationReference(
119
+ slug=p["slug"], description=p.get("description", "")
120
+ )
121
+ for p in publishes_to
122
+ ],
123
+ depends_on=depends_on,
124
+ feeds_into=feeds_into,
125
+ docname=docname,
126
+ )
127
+
128
+ # Add to repository
129
+ self.hcd_context.accelerator_repo.save(accelerator)
130
+
131
+ # Track documented accelerators
132
+ if not hasattr(self.env, "documented_accelerators"):
133
+ self.env.documented_accelerators = set()
134
+ self.env.documented_accelerators.add(slug)
135
+
136
+ # Return placeholder - rendering in doctree-resolved
137
+ node = DefineAcceleratorPlaceholder()
138
+ node["accelerator_slug"] = slug
139
+ return [node]
140
+
141
+
142
+ class AcceleratorIndexDirective(HCDDirective):
143
+ """Generate index table grouped by status.
144
+
145
+ Usage::
146
+
147
+ .. accelerator-index::
148
+ """
149
+
150
+ def run(self):
151
+ return [AcceleratorIndexPlaceholder()]
152
+
153
+
154
+ class AcceleratorsForAppDirective(HCDDirective):
155
+ """List accelerators an app exposes.
156
+
157
+ Usage::
158
+
159
+ .. accelerators-for-app:: vocabulary-tool
160
+ """
161
+
162
+ required_arguments = 1
163
+
164
+ def run(self):
165
+ node = AcceleratorsForAppPlaceholder()
166
+ node["app_slug"] = self.arguments[0]
167
+ return [node]
168
+
169
+
170
+ class DependentAcceleratorsDirective(HCDDirective):
171
+ """List accelerators that depend on or publish to an integration.
172
+
173
+ Usage::
174
+
175
+ .. dependent-accelerators:: kafka
176
+ """
177
+
178
+ required_arguments = 1
179
+
180
+ def run(self):
181
+ node = DependentAcceleratorsPlaceholder()
182
+ node["integration_slug"] = self.arguments[0]
183
+ return [node]
184
+
185
+
186
+ class AcceleratorDependencyDiagramDirective(HCDDirective):
187
+ """Generate PlantUML component diagram of accelerator dependencies.
188
+
189
+ Usage::
190
+
191
+ .. accelerator-dependency-diagram::
192
+ """
193
+
194
+ def run(self):
195
+ return [AcceleratorDependencyDiagramPlaceholder()]
196
+
197
+
198
+ class AcceleratorStatusDirective(HCDDirective):
199
+ """Show status, milestone, and acceptance info for an accelerator.
200
+
201
+ Usage::
202
+
203
+ .. accelerator-status:: vocabulary-catalog
204
+ """
205
+
206
+ required_arguments = 1
207
+
208
+ def run(self):
209
+ slug = self.arguments[0]
210
+ accelerator = self.hcd_context.accelerator_repo.get(slug)
211
+
212
+ if not accelerator:
213
+ return self.empty_result(f"Accelerator '{slug}' not found")
214
+
215
+ result_nodes = []
216
+
217
+ # Status badge
218
+ if accelerator.status:
219
+ status_para = nodes.paragraph()
220
+ status_para += nodes.strong(text="Status: ")
221
+ status_para += nodes.Text(accelerator.status)
222
+ result_nodes.append(status_para)
223
+
224
+ # Milestone
225
+ if accelerator.milestone:
226
+ milestone_para = nodes.paragraph()
227
+ milestone_para += nodes.strong(text="Milestone: ")
228
+ milestone_para += nodes.Text(accelerator.milestone)
229
+ result_nodes.append(milestone_para)
230
+
231
+ # Acceptance criteria
232
+ if accelerator.acceptance:
233
+ acceptance_para = nodes.paragraph()
234
+ acceptance_para += nodes.strong(text="Acceptance: ")
235
+ acceptance_para += nodes.Text(accelerator.acceptance)
236
+ result_nodes.append(acceptance_para)
237
+
238
+ return result_nodes
239
+
240
+
241
+ def build_accelerator_content(slug: str, docname: str, hcd_context):
242
+ """Build content nodes for an accelerator page."""
243
+ from sphinx.addnodes import seealso
244
+
245
+ from ...config import get_config
246
+
247
+ config = get_config()
248
+ prefix = path_to_root(docname)
249
+
250
+ accelerator = hcd_context.accelerator_repo.get(slug)
251
+ if not accelerator:
252
+ para = nodes.paragraph()
253
+ para += nodes.problematic(text=f"Accelerator '{slug}' not found")
254
+ return [para]
255
+
256
+ # Get all entities for cross-references
257
+ all_accelerators = hcd_context.accelerator_repo.list_all()
258
+ all_apps = hcd_context.app_repo.list_all()
259
+ all_integrations = hcd_context.integration_repo.list_all()
260
+ all_code_infos = hcd_context.code_info_repo.list_all()
261
+
262
+ result_nodes = []
263
+
264
+ # Objective/description
265
+ if accelerator.objective:
266
+ obj_para = nodes.paragraph()
267
+ obj_para += nodes.Text(accelerator.objective)
268
+ result_nodes.append(obj_para)
269
+
270
+ # Code info from introspection
271
+ code_info = get_code_info_for_accelerator(accelerator, all_code_infos)
272
+ if code_info:
273
+ if code_info.entities:
274
+ entities_para = nodes.paragraph()
275
+ entities_para += nodes.strong(text="Entities: ")
276
+ entities_para += nodes.Text(", ".join(e.name for e in code_info.entities))
277
+ result_nodes.append(entities_para)
278
+
279
+ if code_info.use_cases:
280
+ uc_para = nodes.paragraph()
281
+ uc_para += nodes.strong(text="Use Cases: ")
282
+ uc_para += nodes.Text(", ".join(uc.name for uc in code_info.use_cases))
283
+ result_nodes.append(uc_para)
284
+
285
+ # Seealso with metadata
286
+ seealso_node = seealso()
287
+
288
+ # Status
289
+ if accelerator.status:
290
+ status_para = nodes.paragraph()
291
+ status_para += nodes.strong(text="Status: ")
292
+ status_para += nodes.Text(accelerator.status)
293
+ seealso_node += status_para
294
+
295
+ # Milestone
296
+ if accelerator.milestone:
297
+ milestone_para = nodes.paragraph()
298
+ milestone_para += nodes.strong(text="Milestone: ")
299
+ milestone_para += nodes.Text(accelerator.milestone)
300
+ seealso_node += milestone_para
301
+
302
+ # Apps
303
+ apps = get_apps_for_accelerator(accelerator, all_apps)
304
+ if apps:
305
+ apps_para = nodes.paragraph()
306
+ apps_para += nodes.strong(text="Apps: ")
307
+ for i, app in enumerate(apps):
308
+ app_path = f"{prefix}{config.get_doc_path('applications')}/{app.slug}.html"
309
+ ref = nodes.reference("", "", refuri=app_path)
310
+ ref += nodes.Text(app.name)
311
+ apps_para += ref
312
+ if i < len(apps) - 1:
313
+ apps_para += nodes.Text(", ")
314
+ seealso_node += apps_para
315
+
316
+ # Sources from (integrations)
317
+ source_integrations = get_source_integrations(accelerator, all_integrations)
318
+ if source_integrations:
319
+ sources_para = nodes.paragraph()
320
+ sources_para += nodes.strong(text="Sources From: ")
321
+ for i, integration in enumerate(source_integrations):
322
+ int_path = (
323
+ f"{prefix}{config.get_doc_path('integrations')}/{integration.slug}.html"
324
+ )
325
+ ref = nodes.reference("", "", refuri=int_path)
326
+ ref += nodes.Text(integration.name)
327
+ sources_para += ref
328
+ if i < len(source_integrations) - 1:
329
+ sources_para += nodes.Text(", ")
330
+ seealso_node += sources_para
331
+
332
+ # Publishes to (integrations)
333
+ publish_integrations = get_publish_integrations(accelerator, all_integrations)
334
+ if publish_integrations:
335
+ publish_para = nodes.paragraph()
336
+ publish_para += nodes.strong(text="Publishes To: ")
337
+ for i, integration in enumerate(publish_integrations):
338
+ int_path = (
339
+ f"{prefix}{config.get_doc_path('integrations')}/{integration.slug}.html"
340
+ )
341
+ ref = nodes.reference("", "", refuri=int_path)
342
+ ref += nodes.Text(integration.name)
343
+ publish_para += ref
344
+ if i < len(publish_integrations) - 1:
345
+ publish_para += nodes.Text(", ")
346
+ seealso_node += publish_para
347
+
348
+ # Depends on (other accelerators)
349
+ if accelerator.depends_on:
350
+ depends_para = nodes.paragraph()
351
+ depends_para += nodes.strong(text="Depends On: ")
352
+ for i, dep_slug in enumerate(accelerator.depends_on):
353
+ accel_path = (
354
+ f"{prefix}{config.get_doc_path('accelerators')}/{dep_slug}.html"
355
+ )
356
+ ref = nodes.reference("", "", refuri=accel_path)
357
+ ref += nodes.Text(dep_slug.replace("-", " ").title())
358
+ depends_para += ref
359
+ if i < len(accelerator.depends_on) - 1:
360
+ depends_para += nodes.Text(", ")
361
+ seealso_node += depends_para
362
+
363
+ # Fed by (accelerators that feed into this one)
364
+ fed_by = get_fed_by_accelerators(accelerator, all_accelerators)
365
+ if fed_by:
366
+ fed_para = nodes.paragraph()
367
+ fed_para += nodes.strong(text="Fed By: ")
368
+ for i, feeder in enumerate(fed_by):
369
+ accel_path = (
370
+ f"{prefix}{config.get_doc_path('accelerators')}/{feeder.slug}.html"
371
+ )
372
+ ref = nodes.reference("", "", refuri=accel_path)
373
+ ref += nodes.Text(feeder.slug.replace("-", " ").title())
374
+ fed_para += ref
375
+ if i < len(fed_by) - 1:
376
+ fed_para += nodes.Text(", ")
377
+ seealso_node += fed_para
378
+
379
+ result_nodes.append(seealso_node)
380
+ return result_nodes
381
+
382
+
383
+ def build_accelerator_index(docname: str, hcd_context):
384
+ """Build accelerator index grouped by status."""
385
+ all_accelerators = hcd_context.accelerator_repo.list_all()
386
+
387
+ if not all_accelerators:
388
+ para = nodes.paragraph()
389
+ para += nodes.emphasis(text="No accelerators defined")
390
+ return [para]
391
+
392
+ # Group by status
393
+ by_status: dict[str, list[Accelerator]] = {}
394
+ for accel in all_accelerators:
395
+ status = accel.status or "unknown"
396
+ by_status.setdefault(status, []).append(accel)
397
+
398
+ result_nodes = []
399
+
400
+ for status in sorted(by_status.keys()):
401
+ accelerators = by_status[status]
402
+
403
+ # Status heading
404
+ heading = nodes.paragraph()
405
+ heading += nodes.strong(text=status.title())
406
+ result_nodes.append(heading)
407
+
408
+ # Accelerator list
409
+ accel_list = nodes.bullet_list()
410
+
411
+ for accel in sorted(accelerators, key=lambda a: a.slug):
412
+ item = nodes.list_item()
413
+ para = nodes.paragraph()
414
+
415
+ # Link to accelerator
416
+ accel_path = f"{accel.slug}.html"
417
+ ref = nodes.reference("", "", refuri=accel_path)
418
+ ref += nodes.Text(accel.slug.replace("-", " ").title())
419
+ para += ref
420
+
421
+ if accel.milestone:
422
+ para += nodes.Text(f" ({accel.milestone})")
423
+
424
+ item += para
425
+ accel_list += item
426
+
427
+ result_nodes.append(accel_list)
428
+
429
+ return result_nodes
430
+
431
+
432
+ def build_accelerators_for_app(app_slug: str, docname: str, hcd_context):
433
+ """Build list of accelerators for an app."""
434
+ from ...config import get_config
435
+
436
+ config = get_config()
437
+ prefix = path_to_root(docname)
438
+
439
+ app = hcd_context.app_repo.get(app_slug)
440
+ if not app:
441
+ para = nodes.paragraph()
442
+ para += nodes.emphasis(text=f"App '{app_slug}' not found")
443
+ return [para]
444
+
445
+ all_accelerators = hcd_context.accelerator_repo.list_all()
446
+
447
+ # Filter to accelerators this app exposes
448
+ matching = [a for a in all_accelerators if a.slug in (app.accelerators or [])]
449
+
450
+ if not matching:
451
+ para = nodes.paragraph()
452
+ para += nodes.emphasis(text=f"No accelerators for app '{app_slug}'")
453
+ return [para]
454
+
455
+ bullet_list = nodes.bullet_list()
456
+
457
+ for accel in sorted(matching, key=lambda a: a.slug):
458
+ item = nodes.list_item()
459
+ para = nodes.paragraph()
460
+
461
+ accel_path = f"{prefix}{config.get_doc_path('accelerators')}/{accel.slug}.html"
462
+ ref = nodes.reference("", "", refuri=accel_path)
463
+ ref += nodes.Text(accel.slug.replace("-", " ").title())
464
+ para += ref
465
+
466
+ item += para
467
+ bullet_list += item
468
+
469
+ return [bullet_list]
470
+
471
+
472
+ def build_dependency_diagram(docname: str, hcd_context):
473
+ """Build PlantUML diagram of accelerator dependencies."""
474
+ try:
475
+ from sphinxcontrib.plantuml import plantuml
476
+ except ImportError:
477
+ para = nodes.paragraph()
478
+ para += nodes.emphasis(text="PlantUML extension not available")
479
+ return [para]
480
+
481
+ all_accelerators = hcd_context.accelerator_repo.list_all()
482
+
483
+ if not all_accelerators:
484
+ para = nodes.paragraph()
485
+ para += nodes.emphasis(text="No accelerators defined")
486
+ return [para]
487
+
488
+ lines = [
489
+ "@startuml",
490
+ "skinparam componentStyle rectangle",
491
+ "skinparam defaultTextAlignment center",
492
+ "",
493
+ ]
494
+
495
+ accel_slugs = {a.slug for a in all_accelerators}
496
+
497
+ # Declare components
498
+ for accel in sorted(all_accelerators, key=lambda a: a.slug):
499
+ accel_id = accel.slug.replace("-", "_")
500
+ lines.append(
501
+ f'component "{accel.slug.replace("-", " ").title()}" as {accel_id}'
502
+ )
503
+
504
+ lines.append("")
505
+
506
+ # Add dependency arrows
507
+ for accel in sorted(all_accelerators, key=lambda a: a.slug):
508
+ accel_id = accel.slug.replace("-", "_")
509
+
510
+ for dep_slug in accel.depends_on:
511
+ if dep_slug in accel_slugs:
512
+ dep_id = dep_slug.replace("-", "_")
513
+ lines.append(f"{accel_id} --> {dep_id}")
514
+
515
+ for feed_slug in accel.feeds_into:
516
+ if feed_slug in accel_slugs:
517
+ feed_id = feed_slug.replace("-", "_")
518
+ lines.append(f"{accel_id} --> {feed_id} : feeds")
519
+
520
+ lines.append("")
521
+ lines.append("@enduml")
522
+
523
+ puml_source = "\n".join(lines)
524
+ node = plantuml(puml_source)
525
+ node["uml"] = puml_source
526
+ node["incdir"] = os.path.dirname(docname)
527
+ node["filename"] = os.path.basename(docname)
528
+
529
+ return [node]
530
+
531
+
532
+ def clear_accelerator_state(app, env, docname):
533
+ """Clear accelerator state when a document is re-read."""
534
+ from ..context import get_hcd_context
535
+
536
+ # Clear documented accelerators tracker
537
+ if (
538
+ hasattr(env, "documented_accelerators")
539
+ and docname in env.documented_accelerators
540
+ ):
541
+ env.documented_accelerators.discard(docname)
542
+
543
+ # Clear accelerators from this document via repository
544
+ hcd_context = get_hcd_context(app)
545
+ hcd_context.accelerator_repo.run_async(
546
+ hcd_context.accelerator_repo.async_repo.clear_by_docname(docname)
547
+ )
548
+
549
+
550
+ def process_accelerator_placeholders(app, doctree, docname):
551
+ """Replace accelerator placeholders with rendered content."""
552
+ from ..context import get_hcd_context
553
+
554
+ hcd_context = get_hcd_context(app)
555
+
556
+ # Process define-accelerator placeholders
557
+ for node in doctree.traverse(DefineAcceleratorPlaceholder):
558
+ slug = node["accelerator_slug"]
559
+ content = build_accelerator_content(slug, docname, hcd_context)
560
+ node.replace_self(content)
561
+
562
+ # Process accelerator-index placeholders
563
+ for node in doctree.traverse(AcceleratorIndexPlaceholder):
564
+ content = build_accelerator_index(docname, hcd_context)
565
+ node.replace_self(content)
566
+
567
+ # Process accelerators-for-app placeholders
568
+ for node in doctree.traverse(AcceleratorsForAppPlaceholder):
569
+ app_slug = node["app_slug"]
570
+ content = build_accelerators_for_app(app_slug, docname, hcd_context)
571
+ node.replace_self(content)
572
+
573
+ # Process accelerator-dependency-diagram placeholders
574
+ for node in doctree.traverse(AcceleratorDependencyDiagramPlaceholder):
575
+ content = build_dependency_diagram(docname, hcd_context)
576
+ node.replace_self(content)