julee 0.1.3__py3-none-any.whl → 0.1.4__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/api/tests/routers/test_documents.py +6 -6
- 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/document/document.py +12 -21
- julee/domain/models/document/tests/test_document.py +14 -34
- julee/domain/use_cases/extract_assemble_data.py +1 -1
- julee/domain/use_cases/initialize_system_data.py +75 -21
- julee/fixtures/documents.yaml +4 -43
- julee/fixtures/knowledge_service_queries.yaml +9 -0
- julee/maintenance/release.py +85 -30
- julee/repositories/memory/document.py +19 -13
- julee/repositories/memory/tests/test_document.py +18 -18
- julee/repositories/minio/document.py +25 -22
- julee/repositories/minio/tests/test_document.py +16 -16
- {julee-0.1.3.dist-info → julee-0.1.4.dist-info}/METADATA +2 -3
- {julee-0.1.3.dist-info → julee-0.1.4.dist-info}/RECORD +27 -28
- julee/fixtures/assembly_specifications.yaml +0 -70
- {julee-0.1.3.dist-info → julee-0.1.4.dist-info}/WHEEL +0 -0
- {julee-0.1.3.dist-info → julee-0.1.4.dist-info}/licenses/LICENSE +0 -0
- {julee-0.1.3.dist-info → julee-0.1.4.dist-info}/top_level.txt +0 -0
julee/docs/sphinx_hcd/apps.py
CHANGED
|
@@ -10,13 +10,12 @@ Provides directives:
|
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
12
|
import yaml
|
|
13
|
-
from pathlib import Path
|
|
14
13
|
from docutils import nodes
|
|
15
|
-
from sphinx.util.docutils import SphinxDirective
|
|
16
14
|
from sphinx.util import logging
|
|
15
|
+
from sphinx.util.docutils import SphinxDirective
|
|
17
16
|
|
|
18
17
|
from .config import get_config
|
|
19
|
-
from .utils import normalize_name,
|
|
18
|
+
from .utils import normalize_name, path_to_root, slugify
|
|
20
19
|
|
|
21
20
|
logger = logging.getLogger(__name__)
|
|
22
21
|
|
|
@@ -31,7 +30,7 @@ def get_app_registry() -> dict:
|
|
|
31
30
|
|
|
32
31
|
def get_documented_apps(env) -> set:
|
|
33
32
|
"""Get documented apps set from env, creating if needed."""
|
|
34
|
-
if not hasattr(env,
|
|
33
|
+
if not hasattr(env, "documented_apps"):
|
|
35
34
|
env.documented_apps = set()
|
|
36
35
|
return env.documented_apps
|
|
37
36
|
|
|
@@ -42,11 +41,12 @@ def scan_app_manifests(app):
|
|
|
42
41
|
_app_registry = {}
|
|
43
42
|
|
|
44
43
|
config = get_config()
|
|
45
|
-
|
|
46
|
-
apps_dir = config.get_path('app_manifests')
|
|
44
|
+
apps_dir = config.get_path("app_manifests")
|
|
47
45
|
|
|
48
46
|
if not apps_dir.exists():
|
|
49
|
-
logger.info(
|
|
47
|
+
logger.info(
|
|
48
|
+
f"apps/ directory not found at {apps_dir} - no app manifests to index"
|
|
49
|
+
)
|
|
50
50
|
return
|
|
51
51
|
|
|
52
52
|
# Scan for app.yaml files
|
|
@@ -68,13 +68,13 @@ def scan_app_manifests(app):
|
|
|
68
68
|
continue
|
|
69
69
|
|
|
70
70
|
_app_registry[app_slug] = {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
71
|
+
"slug": app_slug,
|
|
72
|
+
"name": manifest.get("name", app_slug.replace("-", " ").title()),
|
|
73
|
+
"type": manifest.get("type", "unknown"),
|
|
74
|
+
"status": manifest.get("status"),
|
|
75
|
+
"description": manifest.get("description", "").strip(),
|
|
76
|
+
"accelerators": manifest.get("accelerators", []),
|
|
77
|
+
"manifest_path": str(manifest_path),
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
logger.info(f"Indexed {len(_app_registry)} apps from manifests")
|
|
@@ -118,47 +118,51 @@ def get_personas_for_app(app_slug: str, story_registry: list) -> list[str]:
|
|
|
118
118
|
personas = set()
|
|
119
119
|
app_normalized = normalize_name(app_slug)
|
|
120
120
|
for story in story_registry:
|
|
121
|
-
if story[
|
|
122
|
-
personas.add(story[
|
|
121
|
+
if story["app_normalized"] == app_normalized:
|
|
122
|
+
personas.add(story["persona"])
|
|
123
123
|
return sorted(personas)
|
|
124
124
|
|
|
125
125
|
|
|
126
|
-
def get_journeys_for_app(
|
|
126
|
+
def get_journeys_for_app(
|
|
127
|
+
app_slug: str, story_registry: list, journey_registry: dict
|
|
128
|
+
) -> list[str]:
|
|
127
129
|
"""Get journeys that include stories from this app."""
|
|
128
130
|
# Get story titles for this app
|
|
129
131
|
app_normalized = normalize_name(app_slug)
|
|
130
132
|
app_story_titles = {
|
|
131
|
-
normalize_name(s[
|
|
133
|
+
normalize_name(s["feature"])
|
|
132
134
|
for s in story_registry
|
|
133
|
-
if s[
|
|
135
|
+
if s["app_normalized"] == app_normalized
|
|
134
136
|
}
|
|
135
137
|
|
|
136
138
|
# Find journeys that reference these stories
|
|
137
139
|
journeys = []
|
|
138
140
|
for slug, journey in journey_registry.items():
|
|
139
|
-
for step in journey.get(
|
|
140
|
-
if step.get(
|
|
141
|
-
if normalize_name(step[
|
|
141
|
+
for step in journey.get("steps", []):
|
|
142
|
+
if step.get("type") == "story":
|
|
143
|
+
if normalize_name(step["ref"]) in app_story_titles:
|
|
142
144
|
journeys.append(slug)
|
|
143
145
|
break
|
|
144
146
|
|
|
145
147
|
return sorted(set(journeys))
|
|
146
148
|
|
|
147
149
|
|
|
148
|
-
def get_epics_for_app(
|
|
150
|
+
def get_epics_for_app(
|
|
151
|
+
app_slug: str, story_registry: list, epic_registry: dict
|
|
152
|
+
) -> list[str]:
|
|
149
153
|
"""Get epics that include stories from this app."""
|
|
150
154
|
# Get story titles for this app
|
|
151
155
|
app_normalized = normalize_name(app_slug)
|
|
152
156
|
app_story_titles = {
|
|
153
|
-
normalize_name(s[
|
|
157
|
+
normalize_name(s["feature"])
|
|
154
158
|
for s in story_registry
|
|
155
|
-
if s[
|
|
159
|
+
if s["app_normalized"] == app_normalized
|
|
156
160
|
}
|
|
157
161
|
|
|
158
162
|
# Find epics that reference these stories
|
|
159
163
|
epics = []
|
|
160
164
|
for slug, epic in epic_registry.items():
|
|
161
|
-
for story_title in epic.get(
|
|
165
|
+
for story_title in epic.get("stories", []):
|
|
162
166
|
if normalize_name(story_title) in app_story_titles:
|
|
163
167
|
epics.append(slug)
|
|
164
168
|
break
|
|
@@ -184,12 +188,13 @@ class DefineAppDirective(SphinxDirective):
|
|
|
184
188
|
|
|
185
189
|
# Return placeholder - actual rendering in doctree-resolved
|
|
186
190
|
node = DefineAppPlaceholder()
|
|
187
|
-
node[
|
|
191
|
+
node["app_slug"] = app_slug
|
|
188
192
|
return [node]
|
|
189
193
|
|
|
190
194
|
|
|
191
195
|
class DefineAppPlaceholder(nodes.General, nodes.Element):
|
|
192
196
|
"""Placeholder node for define-app, replaced at doctree-resolved."""
|
|
197
|
+
|
|
193
198
|
pass
|
|
194
199
|
|
|
195
200
|
|
|
@@ -208,6 +213,7 @@ class AppIndexDirective(SphinxDirective):
|
|
|
208
213
|
|
|
209
214
|
class AppIndexPlaceholder(nodes.General, nodes.Element):
|
|
210
215
|
"""Placeholder node for app-index, replaced at doctree-resolved."""
|
|
216
|
+
|
|
211
217
|
pass
|
|
212
218
|
|
|
213
219
|
|
|
@@ -224,17 +230,24 @@ class AppsForPersonaDirective(SphinxDirective):
|
|
|
224
230
|
|
|
225
231
|
def run(self):
|
|
226
232
|
node = AppsForPersonaPlaceholder()
|
|
227
|
-
node[
|
|
233
|
+
node["persona"] = self.arguments[0]
|
|
228
234
|
return [node]
|
|
229
235
|
|
|
230
236
|
|
|
231
237
|
class AppsForPersonaPlaceholder(nodes.General, nodes.Element):
|
|
232
238
|
"""Placeholder node for apps-for-persona, replaced at doctree-resolved."""
|
|
239
|
+
|
|
233
240
|
pass
|
|
234
241
|
|
|
235
242
|
|
|
236
|
-
def build_app_content(
|
|
237
|
-
|
|
243
|
+
def build_app_content(
|
|
244
|
+
app_slug: str,
|
|
245
|
+
docname: str,
|
|
246
|
+
story_registry: list,
|
|
247
|
+
journey_registry: dict,
|
|
248
|
+
epic_registry: dict,
|
|
249
|
+
known_personas: set,
|
|
250
|
+
):
|
|
238
251
|
"""Build the content nodes for an app."""
|
|
239
252
|
from sphinx.addnodes import seealso
|
|
240
253
|
|
|
@@ -251,13 +264,15 @@ def build_app_content(app_slug: str, docname: str, story_registry: list,
|
|
|
251
264
|
prefix = path_to_root(docname)
|
|
252
265
|
|
|
253
266
|
# Description first
|
|
254
|
-
if app_data[
|
|
267
|
+
if app_data["description"]:
|
|
255
268
|
desc_para = nodes.paragraph()
|
|
256
|
-
desc_para += nodes.Text(app_data[
|
|
269
|
+
desc_para += nodes.Text(app_data["description"])
|
|
257
270
|
result_nodes.append(desc_para)
|
|
258
271
|
|
|
259
272
|
# Stories count and link
|
|
260
|
-
app_stories = [
|
|
273
|
+
app_stories = [
|
|
274
|
+
s for s in story_registry if s["app_normalized"] == normalize_name(app_slug)
|
|
275
|
+
]
|
|
261
276
|
|
|
262
277
|
if app_stories:
|
|
263
278
|
story_count = len(app_stories)
|
|
@@ -275,20 +290,20 @@ def build_app_content(app_slug: str, docname: str, story_registry: list,
|
|
|
275
290
|
|
|
276
291
|
# Type
|
|
277
292
|
type_labels = {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
293
|
+
"staff": "Staff Application",
|
|
294
|
+
"external": "External Application",
|
|
295
|
+
"member-tool": "Member Tool",
|
|
281
296
|
}
|
|
282
297
|
type_para = nodes.paragraph()
|
|
283
298
|
type_para += nodes.strong(text="Type: ")
|
|
284
|
-
type_para += nodes.Text(type_labels.get(app_data[
|
|
299
|
+
type_para += nodes.Text(type_labels.get(app_data["type"], app_data["type"]))
|
|
285
300
|
seealso_node += type_para
|
|
286
301
|
|
|
287
302
|
# Status (if present)
|
|
288
|
-
if app_data[
|
|
303
|
+
if app_data["status"]:
|
|
289
304
|
status_para = nodes.paragraph()
|
|
290
305
|
status_para += nodes.strong(text="Status: ")
|
|
291
|
-
status_para += nodes.Text(app_data[
|
|
306
|
+
status_para += nodes.Text(app_data["status"])
|
|
292
307
|
seealso_node += status_para
|
|
293
308
|
|
|
294
309
|
# Personas (derived from stories)
|
|
@@ -298,7 +313,9 @@ def build_app_content(app_slug: str, docname: str, story_registry: list,
|
|
|
298
313
|
persona_para += nodes.strong(text="Personas: ")
|
|
299
314
|
for i, persona in enumerate(personas):
|
|
300
315
|
persona_slug = slugify(persona)
|
|
301
|
-
persona_path =
|
|
316
|
+
persona_path = (
|
|
317
|
+
f"{prefix}{config.get_doc_path('personas')}/{persona_slug}.html"
|
|
318
|
+
)
|
|
302
319
|
persona_normalized = normalize_name(persona)
|
|
303
320
|
|
|
304
321
|
if persona_normalized in known_personas:
|
|
@@ -319,7 +336,9 @@ def build_app_content(app_slug: str, docname: str, story_registry: list,
|
|
|
319
336
|
journey_para = nodes.paragraph()
|
|
320
337
|
journey_para += nodes.strong(text="Journeys: ")
|
|
321
338
|
for i, journey_slug in enumerate(journeys):
|
|
322
|
-
journey_path =
|
|
339
|
+
journey_path = (
|
|
340
|
+
f"{prefix}{config.get_doc_path('journeys')}/{journey_slug}.html"
|
|
341
|
+
)
|
|
323
342
|
ref = nodes.reference("", "", refuri=journey_path)
|
|
324
343
|
ref += nodes.Text(journey_slug.replace("-", " ").title())
|
|
325
344
|
journey_para += ref
|
|
@@ -348,19 +367,15 @@ def build_app_content(app_slug: str, docname: str, story_registry: list,
|
|
|
348
367
|
|
|
349
368
|
def build_app_index(docname: str, story_registry: list):
|
|
350
369
|
"""Build the app index grouped by type."""
|
|
351
|
-
config = get_config()
|
|
352
|
-
|
|
353
370
|
if not _app_registry:
|
|
354
371
|
para = nodes.paragraph()
|
|
355
372
|
para += nodes.emphasis(text="No apps defined")
|
|
356
373
|
return [para]
|
|
357
374
|
|
|
358
|
-
prefix = path_to_root(docname)
|
|
359
|
-
|
|
360
375
|
# Group apps by type
|
|
361
|
-
by_type = {
|
|
376
|
+
by_type = {"staff": [], "external": [], "member-tool": []}
|
|
362
377
|
for slug, app_data in _app_registry.items():
|
|
363
|
-
app_type = app_data[
|
|
378
|
+
app_type = app_data["type"]
|
|
364
379
|
if app_type in by_type:
|
|
365
380
|
by_type[app_type].append((slug, app_data))
|
|
366
381
|
else:
|
|
@@ -369,9 +384,9 @@ def build_app_index(docname: str, story_registry: list):
|
|
|
369
384
|
result_nodes = []
|
|
370
385
|
|
|
371
386
|
type_sections = [
|
|
372
|
-
(
|
|
373
|
-
(
|
|
374
|
-
(
|
|
387
|
+
("staff", "Staff Applications"),
|
|
388
|
+
("external", "External Applications"),
|
|
389
|
+
("member-tool", "Member Tools"),
|
|
375
390
|
]
|
|
376
391
|
|
|
377
392
|
for type_key, type_label in type_sections:
|
|
@@ -387,14 +402,14 @@ def build_app_index(docname: str, story_registry: list):
|
|
|
387
402
|
# App list
|
|
388
403
|
app_list = nodes.bullet_list()
|
|
389
404
|
|
|
390
|
-
for slug, app_data in sorted(apps, key=lambda x: x[1][
|
|
405
|
+
for slug, app_data in sorted(apps, key=lambda x: x[1]["name"]):
|
|
391
406
|
item = nodes.list_item()
|
|
392
407
|
para = nodes.paragraph()
|
|
393
408
|
|
|
394
409
|
# Link to app
|
|
395
410
|
app_path = f"{slug}.html"
|
|
396
411
|
ref = nodes.reference("", "", refuri=app_path)
|
|
397
|
-
ref += nodes.Text(app_data[
|
|
412
|
+
ref += nodes.Text(app_data["name"])
|
|
398
413
|
para += ref
|
|
399
414
|
|
|
400
415
|
# Personas
|
|
@@ -432,13 +447,13 @@ def build_apps_for_persona(docname: str, persona_arg: str, story_registry: list)
|
|
|
432
447
|
|
|
433
448
|
bullet_list = nodes.bullet_list()
|
|
434
449
|
|
|
435
|
-
for slug, app_data in sorted(matching_apps, key=lambda x: x[1][
|
|
450
|
+
for slug, app_data in sorted(matching_apps, key=lambda x: x[1]["name"]):
|
|
436
451
|
item = nodes.list_item()
|
|
437
452
|
para = nodes.paragraph()
|
|
438
453
|
|
|
439
454
|
app_path = f"{prefix}{config.get_doc_path('applications')}/{slug}.html"
|
|
440
455
|
ref = nodes.reference("", "", refuri=app_path)
|
|
441
|
-
ref += nodes.Text(app_data[
|
|
456
|
+
ref += nodes.Text(app_data["name"])
|
|
442
457
|
para += ref
|
|
443
458
|
|
|
444
459
|
item += para
|
|
@@ -449,7 +464,7 @@ def build_apps_for_persona(docname: str, persona_arg: str, story_registry: list)
|
|
|
449
464
|
|
|
450
465
|
def process_app_placeholders(app, doctree, docname):
|
|
451
466
|
"""Replace app placeholders with rendered content."""
|
|
452
|
-
from . import
|
|
467
|
+
from . import epics, journeys, stories
|
|
453
468
|
|
|
454
469
|
env = app.env
|
|
455
470
|
|
|
@@ -460,10 +475,14 @@ def process_app_placeholders(app, doctree, docname):
|
|
|
460
475
|
|
|
461
476
|
# Process define-app placeholders
|
|
462
477
|
for node in doctree.traverse(DefineAppPlaceholder):
|
|
463
|
-
app_slug = node[
|
|
478
|
+
app_slug = node["app_slug"]
|
|
464
479
|
content = build_app_content(
|
|
465
|
-
app_slug,
|
|
466
|
-
|
|
480
|
+
app_slug,
|
|
481
|
+
docname,
|
|
482
|
+
_story_registry,
|
|
483
|
+
journey_registry,
|
|
484
|
+
epic_registry,
|
|
485
|
+
_known_personas,
|
|
467
486
|
)
|
|
468
487
|
node.replace_self(content)
|
|
469
488
|
|
|
@@ -474,7 +493,7 @@ def process_app_placeholders(app, doctree, docname):
|
|
|
474
493
|
|
|
475
494
|
# Process apps-for-persona placeholders
|
|
476
495
|
for node in doctree.traverse(AppsForPersonaPlaceholder):
|
|
477
|
-
persona = node[
|
|
496
|
+
persona = node["persona"]
|
|
478
497
|
content = build_apps_for_persona(docname, persona, _story_registry)
|
|
479
498
|
node.replace_self(content)
|
|
480
499
|
|
julee/docs/sphinx_hcd/config.py
CHANGED
|
@@ -8,25 +8,25 @@ from copy import deepcopy
|
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
|
|
10
10
|
DEFAULT_CONFIG = {
|
|
11
|
-
|
|
11
|
+
"paths": {
|
|
12
12
|
# Where to find Gherkin feature files: {app}/features/*.feature
|
|
13
|
-
|
|
13
|
+
"feature_files": "tests/e2e/",
|
|
14
14
|
# Where to find app manifests: */app.yaml
|
|
15
|
-
|
|
15
|
+
"app_manifests": "apps/",
|
|
16
16
|
# Where to find integration manifests: */integration.yaml
|
|
17
|
-
|
|
17
|
+
"integration_manifests": "src/integrations/",
|
|
18
18
|
# Where to find bounded context code: {slug}/ directories
|
|
19
|
-
|
|
19
|
+
"bounded_contexts": "src/",
|
|
20
20
|
},
|
|
21
|
-
|
|
21
|
+
"docs_structure": {
|
|
22
22
|
# RST file locations relative to docs root
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
30
|
},
|
|
31
31
|
}
|
|
32
32
|
|
|
@@ -76,7 +76,7 @@ class HCDConfig:
|
|
|
76
76
|
self._project_root = self._docs_dir.parent
|
|
77
77
|
|
|
78
78
|
# Merge user config with defaults
|
|
79
|
-
user_config = getattr(app.config,
|
|
79
|
+
user_config = getattr(app.config, "sphinx_hcd", {}) or {}
|
|
80
80
|
self._config = _deep_merge(DEFAULT_CONFIG, user_config)
|
|
81
81
|
|
|
82
82
|
@property
|
|
@@ -98,7 +98,7 @@ class HCDConfig:
|
|
|
98
98
|
Returns:
|
|
99
99
|
Absolute Path resolved relative to project root
|
|
100
100
|
"""
|
|
101
|
-
rel_path = self._config[
|
|
101
|
+
rel_path = self._config["paths"].get(key, "")
|
|
102
102
|
return self._project_root / rel_path
|
|
103
103
|
|
|
104
104
|
def get_doc_path(self, key: str) -> str:
|
|
@@ -110,7 +110,7 @@ class HCDConfig:
|
|
|
110
110
|
Returns:
|
|
111
111
|
Relative path string for use in doc references
|
|
112
112
|
"""
|
|
113
|
-
return self._config[
|
|
113
|
+
return self._config["docs_structure"].get(key, key)
|
|
114
114
|
|
|
115
115
|
|
|
116
116
|
# Module-level config instance, set by setup()
|