julee 0.1.3__py3-none-any.whl → 0.1.5__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.
- julee/__init__.py +1 -1
- julee/api/tests/routers/test_assembly_specifications.py +2 -0
- julee/api/tests/routers/test_documents.py +8 -6
- julee/api/tests/routers/test_knowledge_service_configs.py +2 -0
- julee/api/tests/routers/test_knowledge_service_queries.py +2 -0
- julee/api/tests/routers/test_system.py +2 -0
- julee/api/tests/routers/test_workflows.py +2 -0
- julee/api/tests/test_app.py +2 -0
- julee/api/tests/test_dependencies.py +2 -0
- julee/api/tests/test_requests.py +2 -0
- julee/contrib/polling/__init__.py +22 -19
- julee/contrib/polling/apps/__init__.py +17 -0
- julee/contrib/polling/apps/worker/__init__.py +17 -0
- julee/contrib/polling/apps/worker/pipelines.py +288 -0
- julee/contrib/polling/domain/__init__.py +7 -9
- julee/contrib/polling/domain/models/__init__.py +6 -7
- julee/contrib/polling/domain/models/polling_config.py +18 -1
- julee/contrib/polling/domain/services/__init__.py +6 -5
- julee/contrib/polling/domain/services/poller.py +1 -1
- julee/contrib/polling/infrastructure/__init__.py +9 -8
- julee/contrib/polling/infrastructure/services/__init__.py +6 -5
- julee/contrib/polling/infrastructure/services/polling/__init__.py +6 -5
- julee/contrib/polling/infrastructure/services/polling/http/__init__.py +6 -5
- julee/contrib/polling/infrastructure/services/polling/http/http_poller_service.py +5 -2
- julee/contrib/polling/infrastructure/temporal/__init__.py +12 -12
- julee/contrib/polling/infrastructure/temporal/activities.py +1 -1
- julee/contrib/polling/infrastructure/temporal/manager.py +291 -0
- julee/contrib/polling/infrastructure/temporal/proxies.py +1 -1
- julee/contrib/polling/tests/unit/apps/worker/test_pipelines.py +580 -0
- julee/contrib/polling/tests/unit/infrastructure/services/polling/http/test_http_poller_service.py +40 -2
- julee/contrib/polling/tests/unit/infrastructure/temporal/__init__.py +7 -0
- julee/contrib/polling/tests/unit/infrastructure/temporal/test_manager.py +475 -0
- julee/docs/sphinx_hcd/__init__.py +4 -10
- julee/docs/sphinx_hcd/accelerators.py +277 -180
- julee/docs/sphinx_hcd/apps.py +78 -59
- julee/docs/sphinx_hcd/config.py +16 -16
- julee/docs/sphinx_hcd/epics.py +47 -42
- julee/docs/sphinx_hcd/integrations.py +53 -49
- julee/docs/sphinx_hcd/journeys.py +124 -110
- julee/docs/sphinx_hcd/personas.py +75 -53
- julee/docs/sphinx_hcd/stories.py +99 -71
- julee/docs/sphinx_hcd/utils.py +23 -18
- julee/domain/models/assembly/tests/test_assembly.py +2 -0
- julee/domain/models/assembly_specification/tests/test_assembly_specification.py +2 -0
- julee/domain/models/assembly_specification/tests/test_knowledge_service_query.py +2 -0
- julee/domain/models/custom_fields/tests/test_custom_fields.py +2 -0
- julee/domain/models/document/document.py +12 -21
- julee/domain/models/document/tests/test_document.py +16 -34
- julee/domain/models/policy/tests/test_document_policy_validation.py +2 -0
- julee/domain/models/policy/tests/test_policy.py +2 -0
- julee/domain/use_cases/extract_assemble_data.py +1 -1
- julee/domain/use_cases/initialize_system_data.py +75 -21
- julee/domain/use_cases/tests/test_extract_assemble_data.py +2 -0
- julee/domain/use_cases/tests/test_initialize_system_data.py +2 -0
- julee/domain/use_cases/tests/test_validate_document.py +2 -0
- julee/fixtures/documents.yaml +4 -43
- julee/fixtures/knowledge_service_queries.yaml +9 -0
- julee/maintenance/release.py +90 -30
- julee/repositories/memory/document.py +19 -13
- julee/repositories/memory/tests/test_document.py +20 -18
- julee/repositories/memory/tests/test_document_policy_validation.py +2 -0
- julee/repositories/memory/tests/test_policy.py +2 -0
- julee/repositories/minio/document.py +25 -22
- julee/repositories/minio/tests/test_assembly.py +2 -0
- julee/repositories/minio/tests/test_assembly_specification.py +2 -0
- julee/repositories/minio/tests/test_client_protocol.py +3 -0
- julee/repositories/minio/tests/test_document.py +18 -16
- julee/repositories/minio/tests/test_document_policy_validation.py +2 -0
- julee/repositories/minio/tests/test_knowledge_service_config.py +2 -0
- julee/repositories/minio/tests/test_knowledge_service_query.py +2 -0
- julee/repositories/minio/tests/test_policy.py +2 -0
- julee/services/knowledge_service/anthropic/tests/test_knowledge_service.py +2 -0
- julee/services/knowledge_service/memory/test_knowledge_service.py +2 -0
- julee/services/knowledge_service/test_factory.py +2 -0
- julee/util/tests/test_decorators.py +2 -0
- julee-0.1.5.dist-info/METADATA +103 -0
- {julee-0.1.3.dist-info → julee-0.1.5.dist-info}/RECORD +80 -74
- julee/fixtures/assembly_specifications.yaml +0 -70
- julee-0.1.3.dist-info/METADATA +0 -198
- {julee-0.1.3.dist-info → julee-0.1.5.dist-info}/WHEEL +0 -0
- {julee-0.1.3.dist-info → julee-0.1.5.dist-info}/licenses/LICENSE +0 -0
- {julee-0.1.3.dist-info → julee-0.1.5.dist-info}/top_level.txt +0 -0
|
@@ -14,28 +14,33 @@ Provides directives:
|
|
|
14
14
|
- journeys-for-persona: List journeys for a specific persona
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
|
-
import re
|
|
18
17
|
from docutils import nodes
|
|
19
18
|
from docutils.parsers.rst import directives
|
|
20
|
-
from sphinx.util.docutils import SphinxDirective
|
|
21
19
|
from sphinx.util import logging
|
|
20
|
+
from sphinx.util.docutils import SphinxDirective
|
|
22
21
|
|
|
23
22
|
from .config import get_config
|
|
24
|
-
from .utils import
|
|
23
|
+
from .utils import (
|
|
24
|
+
normalize_name,
|
|
25
|
+
parse_csv_option,
|
|
26
|
+
parse_list_option,
|
|
27
|
+
path_to_root,
|
|
28
|
+
slugify,
|
|
29
|
+
)
|
|
25
30
|
|
|
26
31
|
logger = logging.getLogger(__name__)
|
|
27
32
|
|
|
28
33
|
|
|
29
34
|
def get_journey_registry(env):
|
|
30
35
|
"""Get or create the journey registry on the environment."""
|
|
31
|
-
if not hasattr(env,
|
|
36
|
+
if not hasattr(env, "journey_registry"):
|
|
32
37
|
env.journey_registry = {}
|
|
33
38
|
return env.journey_registry
|
|
34
39
|
|
|
35
40
|
|
|
36
41
|
def get_current_journey(env):
|
|
37
42
|
"""Get or create the current journey tracker on the environment."""
|
|
38
|
-
if not hasattr(env,
|
|
43
|
+
if not hasattr(env, "journey_current"):
|
|
39
44
|
env.journey_current = {}
|
|
40
45
|
return env.journey_current
|
|
41
46
|
|
|
@@ -89,12 +94,12 @@ class DefineJourneyDirective(SphinxDirective):
|
|
|
89
94
|
required_arguments = 1 # journey slug
|
|
90
95
|
has_content = True
|
|
91
96
|
option_spec = {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
97
|
+
"persona": directives.unchanged_required,
|
|
98
|
+
"intent": directives.unchanged,
|
|
99
|
+
"outcome": directives.unchanged,
|
|
100
|
+
"depends-on": directives.unchanged,
|
|
101
|
+
"preconditions": directives.unchanged,
|
|
102
|
+
"postconditions": directives.unchanged,
|
|
98
103
|
}
|
|
99
104
|
|
|
100
105
|
def run(self):
|
|
@@ -105,32 +110,32 @@ class DefineJourneyDirective(SphinxDirective):
|
|
|
105
110
|
docname = self.env.docname
|
|
106
111
|
|
|
107
112
|
# Parse options
|
|
108
|
-
persona = self.options.get(
|
|
109
|
-
intent = self.options.get(
|
|
110
|
-
outcome = self.options.get(
|
|
111
|
-
depends_on = parse_csv_option(self.options.get(
|
|
112
|
-
preconditions = parse_list_option(self.options.get(
|
|
113
|
-
postconditions = parse_list_option(self.options.get(
|
|
113
|
+
persona = self.options.get("persona", "").strip()
|
|
114
|
+
intent = self.options.get("intent", "").strip()
|
|
115
|
+
outcome = self.options.get("outcome", "").strip()
|
|
116
|
+
depends_on = parse_csv_option(self.options.get("depends-on", ""))
|
|
117
|
+
preconditions = parse_list_option(self.options.get("preconditions", ""))
|
|
118
|
+
postconditions = parse_list_option(self.options.get("postconditions", ""))
|
|
114
119
|
|
|
115
120
|
# Goal is the directive content (what they do)
|
|
116
|
-
goal =
|
|
121
|
+
goal = "\n".join(self.content).strip()
|
|
117
122
|
|
|
118
123
|
# Register the journey in environment
|
|
119
124
|
journey_registry = get_journey_registry(self.env)
|
|
120
125
|
current_journey = get_current_journey(self.env)
|
|
121
126
|
|
|
122
127
|
journey_data = {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
128
|
+
"slug": journey_slug,
|
|
129
|
+
"persona": persona,
|
|
130
|
+
"persona_normalized": normalize_name(persona),
|
|
131
|
+
"intent": intent,
|
|
132
|
+
"outcome": outcome,
|
|
133
|
+
"goal": goal,
|
|
134
|
+
"depends_on": depends_on,
|
|
135
|
+
"preconditions": preconditions,
|
|
136
|
+
"postconditions": postconditions,
|
|
137
|
+
"steps": [], # Will be populated by step-story/step-epic/step-phase
|
|
138
|
+
"docname": docname,
|
|
134
139
|
}
|
|
135
140
|
journey_registry[journey_slug] = journey_data
|
|
136
141
|
current_journey[docname] = journey_slug
|
|
@@ -151,7 +156,9 @@ class DefineJourneyDirective(SphinxDirective):
|
|
|
151
156
|
intro_para += nodes.Text("The ")
|
|
152
157
|
|
|
153
158
|
persona_slug = slugify(persona)
|
|
154
|
-
persona_path =
|
|
159
|
+
persona_path = (
|
|
160
|
+
f"{prefix}{config.get_doc_path('personas')}/{persona_slug}.html"
|
|
161
|
+
)
|
|
155
162
|
persona_valid = normalize_name(persona) in _known_personas
|
|
156
163
|
|
|
157
164
|
if persona_valid:
|
|
@@ -181,7 +188,9 @@ class DefineJourneyDirective(SphinxDirective):
|
|
|
181
188
|
intro_para += nodes.Text("The goal of the ")
|
|
182
189
|
|
|
183
190
|
persona_slug = slugify(persona)
|
|
184
|
-
persona_path =
|
|
191
|
+
persona_path = (
|
|
192
|
+
f"{prefix}{config.get_doc_path('personas')}/{persona_slug}.html"
|
|
193
|
+
)
|
|
185
194
|
persona_valid = normalize_name(persona) in _known_personas
|
|
186
195
|
|
|
187
196
|
if persona_valid:
|
|
@@ -203,8 +212,8 @@ class DefineJourneyDirective(SphinxDirective):
|
|
|
203
212
|
|
|
204
213
|
# Add a placeholder for steps (will be filled in doctree-resolved)
|
|
205
214
|
steps_placeholder = nodes.container()
|
|
206
|
-
steps_placeholder[
|
|
207
|
-
steps_placeholder[
|
|
215
|
+
steps_placeholder["classes"].append("journey-steps-placeholder")
|
|
216
|
+
steps_placeholder["journey_slug"] = journey_slug
|
|
208
217
|
result_nodes.append(steps_placeholder)
|
|
209
218
|
|
|
210
219
|
return result_nodes
|
|
@@ -231,10 +240,12 @@ class StepStoryDirective(SphinxDirective):
|
|
|
231
240
|
|
|
232
241
|
journey_slug = current_journey.get(docname)
|
|
233
242
|
if journey_slug and journey_slug in journey_registry:
|
|
234
|
-
journey_registry[journey_slug][
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
243
|
+
journey_registry[journey_slug]["steps"].append(
|
|
244
|
+
{
|
|
245
|
+
"type": "story",
|
|
246
|
+
"ref": story_title,
|
|
247
|
+
}
|
|
248
|
+
)
|
|
238
249
|
|
|
239
250
|
# Return empty - rendering happens in doctree-resolved
|
|
240
251
|
return []
|
|
@@ -260,10 +271,12 @@ class StepEpicDirective(SphinxDirective):
|
|
|
260
271
|
|
|
261
272
|
journey_slug = current_journey.get(docname)
|
|
262
273
|
if journey_slug and journey_slug in journey_registry:
|
|
263
|
-
journey_registry[journey_slug][
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
274
|
+
journey_registry[journey_slug]["steps"].append(
|
|
275
|
+
{
|
|
276
|
+
"type": "epic",
|
|
277
|
+
"ref": epic_slug,
|
|
278
|
+
}
|
|
279
|
+
)
|
|
267
280
|
|
|
268
281
|
# Return empty - rendering happens in doctree-resolved
|
|
269
282
|
return []
|
|
@@ -286,7 +299,7 @@ class StepPhaseDirective(SphinxDirective):
|
|
|
286
299
|
def run(self):
|
|
287
300
|
phase_title = self.arguments[0]
|
|
288
301
|
docname = self.env.docname
|
|
289
|
-
description =
|
|
302
|
+
description = "\n".join(self.content).strip()
|
|
290
303
|
|
|
291
304
|
# Add to current journey's steps
|
|
292
305
|
journey_registry = get_journey_registry(self.env)
|
|
@@ -294,11 +307,13 @@ class StepPhaseDirective(SphinxDirective):
|
|
|
294
307
|
|
|
295
308
|
journey_slug = current_journey.get(docname)
|
|
296
309
|
if journey_slug and journey_slug in journey_registry:
|
|
297
|
-
journey_registry[journey_slug][
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
310
|
+
journey_registry[journey_slug]["steps"].append(
|
|
311
|
+
{
|
|
312
|
+
"type": "phase",
|
|
313
|
+
"ref": phase_title,
|
|
314
|
+
"description": description,
|
|
315
|
+
}
|
|
316
|
+
)
|
|
302
317
|
|
|
303
318
|
# Return empty - rendering happens in doctree-resolved
|
|
304
319
|
return []
|
|
@@ -320,8 +335,6 @@ class JourneyIndexDirective(SphinxDirective):
|
|
|
320
335
|
para += nodes.emphasis(text="No journeys defined")
|
|
321
336
|
return [para]
|
|
322
337
|
|
|
323
|
-
docname = self.env.docname
|
|
324
|
-
|
|
325
338
|
result_nodes = []
|
|
326
339
|
bullet_list = nodes.bullet_list()
|
|
327
340
|
|
|
@@ -338,13 +351,13 @@ class JourneyIndexDirective(SphinxDirective):
|
|
|
338
351
|
para += journey_ref
|
|
339
352
|
|
|
340
353
|
# Persona in parentheses
|
|
341
|
-
if journey[
|
|
354
|
+
if journey["persona"]:
|
|
342
355
|
para += nodes.Text(f" ({journey['persona']})")
|
|
343
356
|
|
|
344
357
|
item += para
|
|
345
358
|
|
|
346
359
|
# Intent as sub-paragraph (preferred), fall back to goal
|
|
347
|
-
display_text = journey.get(
|
|
360
|
+
display_text = journey.get("intent") or journey.get("goal", "")
|
|
348
361
|
if display_text:
|
|
349
362
|
desc_para = nodes.paragraph()
|
|
350
363
|
# Truncate if too long
|
|
@@ -361,6 +374,7 @@ class JourneyIndexDirective(SphinxDirective):
|
|
|
361
374
|
|
|
362
375
|
class JourneyDependencyGraphPlaceholder(nodes.General, nodes.Element):
|
|
363
376
|
"""Placeholder node for journey dependency graph, replaced at doctree-resolved."""
|
|
377
|
+
|
|
364
378
|
pass
|
|
365
379
|
|
|
366
380
|
|
|
@@ -401,13 +415,13 @@ def build_dependency_graph_node(env):
|
|
|
401
415
|
# Add all journeys as components
|
|
402
416
|
for slug in sorted(journey_registry.keys()):
|
|
403
417
|
title = slug.replace("-", " ").title()
|
|
404
|
-
lines.append(f
|
|
418
|
+
lines.append(f"[{title}] as {slug.replace('-', '_')}")
|
|
405
419
|
|
|
406
420
|
lines.append("")
|
|
407
421
|
|
|
408
422
|
# Add dependency arrows (A depends on B => A --> B)
|
|
409
423
|
for slug, journey in sorted(journey_registry.items()):
|
|
410
|
-
for dep in journey[
|
|
424
|
+
for dep in journey["depends_on"]:
|
|
411
425
|
if dep in journey_registry:
|
|
412
426
|
from_id = slug.replace("-", "_")
|
|
413
427
|
to_id = dep.replace("-", "_")
|
|
@@ -420,9 +434,9 @@ def build_dependency_graph_node(env):
|
|
|
420
434
|
|
|
421
435
|
# Use the sphinxcontrib.plantuml extension's node type
|
|
422
436
|
puml_node = plantuml(puml_content)
|
|
423
|
-
puml_node[
|
|
424
|
-
puml_node[
|
|
425
|
-
puml_node[
|
|
437
|
+
puml_node["uml"] = puml_content
|
|
438
|
+
puml_node["incdir"] = ""
|
|
439
|
+
puml_node["filename"] = "journey-dependency-graph"
|
|
426
440
|
|
|
427
441
|
return puml_node
|
|
428
442
|
|
|
@@ -460,25 +474,32 @@ class JourneysForPersonaDirective(SphinxDirective):
|
|
|
460
474
|
journey_registry = get_journey_registry(self.env)
|
|
461
475
|
|
|
462
476
|
# Find journeys for this persona
|
|
463
|
-
journeys = [
|
|
464
|
-
|
|
477
|
+
journeys = [
|
|
478
|
+
j
|
|
479
|
+
for j in journey_registry.values()
|
|
480
|
+
if j["persona_normalized"] == persona_normalized
|
|
481
|
+
]
|
|
465
482
|
|
|
466
483
|
if not journeys:
|
|
467
484
|
para = nodes.paragraph()
|
|
468
|
-
para += nodes.emphasis(
|
|
485
|
+
para += nodes.emphasis(
|
|
486
|
+
text=f"No journeys found for persona '{persona_arg}'"
|
|
487
|
+
)
|
|
469
488
|
return [para]
|
|
470
489
|
|
|
471
490
|
prefix = path_to_root(docname)
|
|
472
491
|
|
|
473
492
|
bullet_list = nodes.bullet_list()
|
|
474
493
|
|
|
475
|
-
for journey in sorted(journeys, key=lambda j: j[
|
|
494
|
+
for journey in sorted(journeys, key=lambda j: j["slug"]):
|
|
476
495
|
item = nodes.list_item()
|
|
477
496
|
para = nodes.paragraph()
|
|
478
497
|
|
|
479
|
-
journey_path =
|
|
498
|
+
journey_path = (
|
|
499
|
+
f"{prefix}{config.get_doc_path('journeys')}/{journey['slug']}.html"
|
|
500
|
+
)
|
|
480
501
|
journey_ref = nodes.reference("", "", refuri=journey_path)
|
|
481
|
-
journey_ref += nodes.Text(journey[
|
|
502
|
+
journey_ref += nodes.Text(journey["slug"].replace("-", " ").title())
|
|
482
503
|
para += journey_ref
|
|
483
504
|
|
|
484
505
|
item += para
|
|
@@ -496,8 +517,9 @@ def clear_journey_state(app, env, docname):
|
|
|
496
517
|
del current_journey[docname]
|
|
497
518
|
|
|
498
519
|
# Remove journeys defined in this document
|
|
499
|
-
to_remove = [
|
|
500
|
-
|
|
520
|
+
to_remove = [
|
|
521
|
+
slug for slug, j in journey_registry.items() if j["docname"] == docname
|
|
522
|
+
]
|
|
501
523
|
for slug in to_remove:
|
|
502
524
|
del journey_registry[slug]
|
|
503
525
|
|
|
@@ -510,26 +532,26 @@ def validate_journeys(app, env):
|
|
|
510
532
|
_story_registry = stories.get_story_registry()
|
|
511
533
|
_known_personas = stories.get_known_personas()
|
|
512
534
|
|
|
513
|
-
story_titles = {normalize_name(s[
|
|
535
|
+
story_titles = {normalize_name(s["feature"]) for s in _story_registry}
|
|
514
536
|
|
|
515
537
|
for slug, journey in journey_registry.items():
|
|
516
538
|
# Validate persona
|
|
517
|
-
if journey[
|
|
539
|
+
if journey["persona"] and journey["persona_normalized"] not in _known_personas:
|
|
518
540
|
logger.warning(
|
|
519
541
|
f"Journey '{slug}' references unknown persona: '{journey['persona']}'"
|
|
520
542
|
)
|
|
521
543
|
|
|
522
544
|
# Validate depends-on journeys
|
|
523
|
-
for dep in journey[
|
|
545
|
+
for dep in journey["depends_on"]:
|
|
524
546
|
if dep not in journey_registry:
|
|
525
547
|
logger.warning(
|
|
526
548
|
f"Journey '{slug}' references unknown dependency: '{dep}'"
|
|
527
549
|
)
|
|
528
550
|
|
|
529
551
|
# Validate story steps
|
|
530
|
-
for step in journey[
|
|
531
|
-
if step[
|
|
532
|
-
if normalize_name(step[
|
|
552
|
+
for step in journey["steps"]:
|
|
553
|
+
if step["type"] == "story":
|
|
554
|
+
if normalize_name(step["ref"]) not in story_titles:
|
|
533
555
|
logger.warning(
|
|
534
556
|
f"Journey '{slug}' references unknown story: '{step['ref']}'"
|
|
535
557
|
)
|
|
@@ -594,7 +616,7 @@ def build_epic_node(epic_slug: str, docname: str):
|
|
|
594
616
|
|
|
595
617
|
def render_journey_steps(journey: dict, docname: str):
|
|
596
618
|
"""Render journey steps as a numbered list with phases grouping stories."""
|
|
597
|
-
steps = journey[
|
|
619
|
+
steps = journey["steps"]
|
|
598
620
|
if not steps:
|
|
599
621
|
return None
|
|
600
622
|
|
|
@@ -603,50 +625,46 @@ def render_journey_steps(journey: dict, docname: str):
|
|
|
603
625
|
current_phase = None
|
|
604
626
|
|
|
605
627
|
for step in steps:
|
|
606
|
-
if step[
|
|
628
|
+
if step["type"] == "phase":
|
|
607
629
|
# Start a new phase
|
|
608
630
|
current_phase = {
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
631
|
+
"title": step["ref"],
|
|
632
|
+
"description": step.get("description", ""),
|
|
633
|
+
"items": [],
|
|
612
634
|
}
|
|
613
635
|
phases.append(current_phase)
|
|
614
|
-
elif step[
|
|
636
|
+
elif step["type"] in ("story", "epic"):
|
|
615
637
|
if current_phase is None:
|
|
616
638
|
# Stories before any phase - create implicit phase
|
|
617
|
-
current_phase = {
|
|
618
|
-
'title': None,
|
|
619
|
-
'description': '',
|
|
620
|
-
'items': []
|
|
621
|
-
}
|
|
639
|
+
current_phase = {"title": None, "description": "", "items": []}
|
|
622
640
|
phases.append(current_phase)
|
|
623
|
-
current_phase[
|
|
641
|
+
current_phase["items"].append(step)
|
|
624
642
|
|
|
625
643
|
# Build enumerated list
|
|
626
644
|
enum_list = nodes.enumerated_list()
|
|
627
|
-
enum_list[
|
|
645
|
+
enum_list["enumtype"] = "arabic"
|
|
628
646
|
|
|
629
647
|
for phase in phases:
|
|
630
648
|
list_item = nodes.list_item()
|
|
631
649
|
|
|
632
650
|
# Phase header paragraph: "Title — Description" or just items if no title
|
|
633
|
-
if phase[
|
|
651
|
+
if phase["title"]:
|
|
634
652
|
header_para = nodes.paragraph()
|
|
635
|
-
header_para += nodes.strong(text=phase[
|
|
636
|
-
if phase[
|
|
653
|
+
header_para += nodes.strong(text=phase["title"])
|
|
654
|
+
if phase["description"]:
|
|
637
655
|
header_para += nodes.Text(" — ")
|
|
638
|
-
header_para += nodes.Text(phase[
|
|
656
|
+
header_para += nodes.Text(phase["description"])
|
|
639
657
|
list_item += header_para
|
|
640
658
|
|
|
641
659
|
# Nested bullet list for stories/epics
|
|
642
|
-
if phase[
|
|
660
|
+
if phase["items"]:
|
|
643
661
|
bullet_list = nodes.bullet_list()
|
|
644
|
-
for item in phase[
|
|
662
|
+
for item in phase["items"]:
|
|
645
663
|
bullet_item = nodes.list_item()
|
|
646
|
-
if item[
|
|
647
|
-
bullet_item += build_story_node(item[
|
|
648
|
-
elif item[
|
|
649
|
-
bullet_item += build_epic_node(item[
|
|
664
|
+
if item["type"] == "story":
|
|
665
|
+
bullet_item += build_story_node(item["ref"], docname)
|
|
666
|
+
elif item["type"] == "epic":
|
|
667
|
+
bullet_item += build_epic_node(item["ref"], docname)
|
|
650
668
|
bullet_list += bullet_item
|
|
651
669
|
list_item += bullet_list
|
|
652
670
|
|
|
@@ -655,14 +673,15 @@ def render_journey_steps(journey: dict, docname: str):
|
|
|
655
673
|
return enum_list
|
|
656
674
|
|
|
657
675
|
|
|
658
|
-
def make_labelled_list(
|
|
676
|
+
def make_labelled_list(
|
|
677
|
+
term: str, items: list, env, docname: str = None, item_type: str = "text"
|
|
678
|
+
):
|
|
659
679
|
"""Create a labelled bullet list with term as heading.
|
|
660
680
|
|
|
661
681
|
Uses 'simple' class on bullet list for compact vertical spacing.
|
|
662
682
|
|
|
663
683
|
item_type can be 'text' (plain text) or 'journey' (journey links).
|
|
664
684
|
"""
|
|
665
|
-
config = get_config()
|
|
666
685
|
journey_registry = get_journey_registry(env)
|
|
667
686
|
container = nodes.container()
|
|
668
687
|
|
|
@@ -679,7 +698,7 @@ def make_labelled_list(term: str, items: list, env, docname: str = None, item_ty
|
|
|
679
698
|
# Use inline container for content to avoid paragraph gaps
|
|
680
699
|
inline = nodes.inline()
|
|
681
700
|
|
|
682
|
-
if item_type ==
|
|
701
|
+
if item_type == "journey":
|
|
683
702
|
related_slug = item
|
|
684
703
|
related_path = f"{related_slug}.html"
|
|
685
704
|
if related_slug in journey_registry:
|
|
@@ -719,7 +738,7 @@ def process_journey_steps(app, doctree):
|
|
|
719
738
|
|
|
720
739
|
# Find and replace the steps placeholder
|
|
721
740
|
for node in doctree.traverse(nodes.container):
|
|
722
|
-
if
|
|
741
|
+
if "journey-steps-placeholder" in node.get("classes", []):
|
|
723
742
|
steps_node = render_journey_steps(journey, docname)
|
|
724
743
|
if steps_node:
|
|
725
744
|
node.replace_self(steps_node)
|
|
@@ -728,31 +747,26 @@ def process_journey_steps(app, doctree):
|
|
|
728
747
|
break
|
|
729
748
|
|
|
730
749
|
# Add preconditions if present (after steps)
|
|
731
|
-
if journey[
|
|
732
|
-
doctree += make_labelled_list(
|
|
733
|
-
"Preconditions", journey['preconditions'], env
|
|
734
|
-
)
|
|
750
|
+
if journey["preconditions"]:
|
|
751
|
+
doctree += make_labelled_list("Preconditions", journey["preconditions"], env)
|
|
735
752
|
|
|
736
753
|
# Add postconditions if present
|
|
737
|
-
if journey[
|
|
738
|
-
doctree += make_labelled_list(
|
|
739
|
-
"Postconditions", journey['postconditions'], env
|
|
740
|
-
)
|
|
754
|
+
if journey["postconditions"]:
|
|
755
|
+
doctree += make_labelled_list("Postconditions", journey["postconditions"], env)
|
|
741
756
|
|
|
742
757
|
# Add depends-on journeys if present
|
|
743
|
-
if journey[
|
|
758
|
+
if journey["depends_on"]:
|
|
744
759
|
doctree += make_labelled_list(
|
|
745
|
-
"Depends On", journey[
|
|
760
|
+
"Depends On", journey["depends_on"], env, docname, item_type="journey"
|
|
746
761
|
)
|
|
747
762
|
|
|
748
763
|
# Add depended-on-by journeys (inferred from other journeys' depends_on)
|
|
749
764
|
depended_on_by = [
|
|
750
|
-
j[
|
|
751
|
-
if journey_slug in j['depends_on']
|
|
765
|
+
j["slug"] for j in journey_registry.values() if journey_slug in j["depends_on"]
|
|
752
766
|
]
|
|
753
767
|
if depended_on_by:
|
|
754
768
|
doctree += make_labelled_list(
|
|
755
|
-
"Depended On By", sorted(depended_on_by), env, docname, item_type=
|
|
769
|
+
"Depended On By", sorted(depended_on_by), env, docname, item_type="journey"
|
|
756
770
|
)
|
|
757
771
|
|
|
758
772
|
|